diff options
75 files changed, 3466 insertions, 1270 deletions
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 850b2ba6c..9e3a25ef7 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -35,6 +35,12 @@ $rcmail_config['log_dir'] = 'logs/'; // 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_long'] = 'd.m.Y H:i'; $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'; @@ -2,7 +2,7 @@ /* +-------------------------------------------------------------------------+ | RoundCube Webmail IMAP Client | - | Version 0.2-20080829 | + | Version 0.3-20090419 | | | | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland | | | @@ -36,6 +36,9 @@ $RCMAIL = rcmail::get_instance(); // 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') { // use gzip compression if supported @@ -70,21 +73,29 @@ if ($RCMAIL->action=='error' && !empty($_GET['_code'])) { 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(); - - // set IMAP host - $host = $RCMAIL->autoselect_host(); + $RCMAIL->kill_session(); + $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 @@ if ($RCMAIL->action=='login' && $RCMAIL->task=='mail') { $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 @@ $redirects = 0; $incstep = null; while ($redirects < 5) { $stepfile = !empty($action_map[$RCMAIL->task][$RCMAIL->action]) ? $action_map[$RCMAIL->task][$RCMAIL->action] : strtr($RCMAIL->action, '-', '_') . '.inc'; - + + // execute a plugin action + if (eregi('^plugin.', $RCMAIL->action)) { + $RCMAIL->plugins->exec_action($RCMAIL->action); + break; + } // try to include the step file - if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) { + else if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) { include($incfile); $redirects++; } diff --git a/plugins/additional_message_headers/additional_message_headers.php b/plugins/additional_message_headers/additional_message_headers.php new file mode 100644 index 000000000..92471384e --- /dev/null +++ b/plugins/additional_message_headers/additional_message_headers.php @@ -0,0 +1,42 @@ +<?php + +/** + * Additional Message Headers + * + * Very simple plugin which will read additional headers for outgoing messages from the config file. + * + * Enable the plugin in config/main.inc.php and add your desired headers. + * + * @version 1.0 + * @author Ziba Scott + * @website http://roundcube.net + * + * Example: + * + * $rcmail_config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT']; + * $rcmail_config['additional_message_headers']['X-Originating-IP'] = $_SERVER['REMOTE_ADDR']; + * $rcmail_config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR']; + * if( isset( $_SERVER['MACHINE_NAME'] )) { + * $rcmail_config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')'; + * } + */ +class additional_message_headers extends rcube_plugin +{ + public $task = 'mail'; + + function init() + { + $this->add_hook('outgoing_message_headers', array($this, 'message_headers')); + } + + function message_headers($args){ + + // additional email headers + $additional_headers = rcmail::get_instance()->config->get('additional_message_headers',array()); + foreach($additional_headers as $header=>$value){ + $args['headers'][$header] = $value; + } + + return $args; + } +} diff --git a/plugins/autologon/autologon.php b/plugins/autologon/autologon.php new file mode 100644 index 000000000..c40f2d4eb --- /dev/null +++ b/plugins/autologon/autologon.php @@ -0,0 +1,44 @@ +<?php + +/** + * Sample plugin to try out some hooks. + * This performs an automatic login if accessed from localhost + */ +class autologon extends rcube_plugin +{ + + function init() + { + $this->add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function startup($args) + { + $rcmail = rcmail::get_instance(); + + // change action to login + if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) && !empty($_GET['_autologin']) && $this->is_localhost()) + $args['action'] = 'login'; + + return $args; + } + + function authenticate($args) + { + if (!empty($_GET['_autologin']) && $this->is_localhost()) { + $args['user'] = 'me'; + $args['pass'] = '******'; + $args['host'] = 'localhost'; + } + + return $args; + } + + function is_localhost() + { + return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1'; + } + +} + diff --git a/plugins/database_attachments/database_attachments.php b/plugins/database_attachments/database_attachments.php new file mode 100644 index 000000000..28ccde4b3 --- /dev/null +++ b/plugins/database_attachments/database_attachments.php @@ -0,0 +1,152 @@ +<?php +/** + * Filesystem Attachments + * + * This plugin which provides database backed storage for temporary + * attachment file handling. The primary advantage of this plugin + * is its compatibility with round-robin dns multi-server roundcube + * installations. + * + * This plugin relies on the core filesystem_attachments plugin + * + * @author Ziba Scott <ziba@umich.edu> + * + */ +require_once('plugins/filesystem_attachments/filesystem_attachments.php'); +class database_attachments extends filesystem_attachments +{ + + // A prefix for the cache key used in the session and in the key field of the cache table + private $cache_prefix = "db_attach"; + + /** + * Helper method to generate a unique key for the given attachment file + */ + private function _key($filepath) + { + return $this->cache_prefix.md5(mktime().$filepath.$_SESSION['user_id']); + } + + /** + * Save a newly uploaded attachment + */ + function upload($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + $key = $this->_key($args['path']); + $data = base64_encode(file_get_contents($args['path'])); + + $status = $rcmail->db->query( + "INSERT INTO ".get_table_name('cache')." + (created, user_id, cache_key, data) + VALUES (".$rcmail->db->now().", ?, ?, ?)", + $_SESSION['user_id'], + $key, + $data); + + if ($status) { + $args['id'] = $key; + $args['status'] = true; + unset($args['path']); + } + + return $args; + } + + /** + * Save an attachment from a non-upload source (draft or forward) + */ + function save($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + + $key = $this->_key($args['name']); + $data = base64_encode($args['data']); + + $status = $rcmail->db->query( + "INSERT INTO ".get_table_name('cache')." + (created, user_id, cache_key, data) + VALUES (".$rcmail->db->now().", ?, ?, ?)", + $_SESSION['user_id'], + $key, + $data); + + if ($status) { + $args['id'] = $key; + $args['status'] = true; + } + + return $args; + } + + /** + * Remove an attachment from storage + * This is triggered by the remove attachment button on the compose screen + */ + function remove($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + $status = $rcmail->db->query( + "DELETE FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $args['id']); + + if ($status) { + $args['status'] = true; + } + + return $args; + } + + /** + * When composing an html message, image attachments may be shown + * For this plugin, $this->get_attachment will check the file and + * return it's contents + */ + function display($args) + { + return $this->get_attachment($args); + } + + /** + * When displaying or sending the attachment the file contents are fetched + * using this method. This is also called by the display_attachment hook. + */ + function get_attachment($args) + { + $rcmail = rcmail::get_instance(); + + $sql_result = $rcmail->db->query( + "SELECT cache_id, data + FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $args['id']); + + if ($sql_arr = $rcmail->db->fetch_assoc($sql_result)) { + $args['data'] = base64_decode($sql_arr['data']); + $args['status'] = true; + } + + return $args; + } + + /** + * Delete all temp files associated with this user + */ + function cleanup($args) + { + $rcmail = rcmail::get_instance(); + $rcmail->db->query( + "DELETE FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key like '{$this->cache_prefix}%'", + $_SESSION['user_id']); + } +} diff --git a/plugins/debug_logger/debug_logger.php b/plugins/debug_logger/debug_logger.php new file mode 100644 index 000000000..8cd335187 --- /dev/null +++ b/plugins/debug_logger/debug_logger.php @@ -0,0 +1,146 @@ +<?php + +/** + * Debug Logger + * + * Enhanced logging for debugging purposes. It is not recommened + * to be enabled on production systems without testing because of + * the somewhat increased memory, cpu and disk i/o overhead. + * + * Debug Logger listens for existing console("message") calls and + * introduces start and end tags as well as free form tagging + * which can redirect messages to files. The resulting log files + * provide timing and tag quantity results. + * + * Enable the plugin in config/main.inc.php and add your desired + * log types and files. + * + * @version 1.0 + * @author Ziba Scott + * @website http://roundcube.net + * + * Example: + * + * config/main.inc.php: + * + * // $rcmail_config['debug_logger'][type of logging] = name of file in log_dir + * // The 'master' log includes timing information + * $rcmail_config['debug_logger']['master'] = 'master'; + * // If you want sql messages to also go into a separate file + * $rcmail_config['debug_logger']['sql'] = 'sql'; + * + * index.php (just after $RCMAIL->plugins->init()): + * + * console("my test","start"); + * console("my message"); + * console("my sql calls","start"); + * console("cp -r * /dev/null","shell exec"); + * console("select * from example","sql"); + * console("select * from example","sql"); + * console("select * from example","sql"); + * console("end"); + * console("end"); + * + * + * logs/master (after reloading the main page): + * + * [17-Feb-2009 16:51:37 -0500] start: Task: mail. + * [17-Feb-2009 16:51:37 -0500] start: my test + * [17-Feb-2009 16:51:37 -0500] my message + * [17-Feb-2009 16:51:37 -0500] shell exec: cp -r * /dev/null + * [17-Feb-2009 16:51:37 -0500] start: my sql calls + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] end: my sql calls - 0.0018 seconds shell exec: 1, sql: 3, + * [17-Feb-2009 16:51:37 -0500] end: my test - 0.0055 seconds shell exec: 1, sql: 3, + * [17-Feb-2009 16:51:38 -0500] end: Task: mail. - 0.8854 seconds shell exec: 1, sql: 3, + * + * logs/sql (after reloading the main page): + * + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + */ +class debug_logger extends rcube_plugin +{ + function init() + { + require_once(dirname(__FILE__).'/runlog/runlog.php'); + $this->runlog = new runlog(); + + if(!rcmail::get_instance()->config->get('log_dir')){ + rcmail::get_instance()->config->set('log_dir',INSTALL_PATH.'logs'); + } + + $log_config = rcmail::get_instance()->config->get('debug_logger',array()); + + foreach($log_config as $type=>$file){ + $this->runlog->set_file(rcmail::get_instance()->config->get('log_dir').'/'.$file, $type); + } + + $start_string = ""; + $action = rcmail::get_instance()->action; + $task = rcmail::get_instance()->task; + if($action){ + $start_string .= "Action: ".$action.". "; + } + if($task){ + $start_string .= "Task: ".$task.". "; + } + $this->runlog->start($start_string); + + $this->add_hook('console', array($this, 'console')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function authenticate($args){ + $this->runlog->note('Authenticating '.$args['user'].'@'.$args['host']); + return $args; + } + + function console($args){ + $note = $args[0]; + $type = $args[1]; + + + if(!isset($args[1])){ + // This could be extended to detect types based on the + // file which called console. For now only rcube_imap.inc is supported + $bt = debug_backtrace(true); + $file = $bt[3]['file']; + switch(basename($file)){ + case 'rcube_imap.php': + $type = 'imap'; + break; + default: + $type = FALSE; + break; + } + } + switch($note){ + case 'end': + $type = 'end'; + break; + } + + + switch($type){ + case 'start': + $this->runlog->start($note); + break; + case 'end': + $this->runlog->end(); + break; + default: + $this->runlog->note($note, $type); + break; + } + return $args; + } + + function __destruct(){ + $this->runlog->end(); + } +} +?> diff --git a/plugins/debug_logger/runlog/runlog.php b/plugins/debug_logger/runlog/runlog.php new file mode 100644 index 000000000..c9f672615 --- /dev/null +++ b/plugins/debug_logger/runlog/runlog.php @@ -0,0 +1,227 @@ +<?php + +/** + * runlog + * + * @author Ziba Scott <ziba@umich.edu> + */ +class runlog { + + private $start_time = FALSE; + + private $parent_stack = array(); + + public $print_to_console = FALSE; + + private $file_handles = array(); + + private $indent = 0; + + public $threshold = 0; + + public $tag_count = array(); + + public $timestamp = "d-M-Y H:i:s O"; + + public $max_line_size = 150; + + private $run_log = array(); + + function runlog() + { + $this->start_time = microtime( TRUE ); + } + + public function start( $name, $tag = FALSE ) + { + $this->run_log[] = array( 'type' => 'start', + 'tag' => $tag, + 'index' => count($this->run_log), + 'value' => $name, + 'time' => microtime( TRUE ), + 'parents' => $this->parent_stack, + 'ended' => false, + ); + $this->parent_stack[] = $name; + + $this->print_to_console("start: ".$name, $tag, 'start'); + $this->print_to_file("start: ".$name, $tag, 'start'); + $this->indent++; + } + + public function end() + { + $name = array_pop( $this->parent_stack ); + foreach ( $this->run_log as $k => $entry ) { + if ( $entry['value'] == $name && $entry['type'] == 'start' && $entry['ended'] == false) { + $lastk = $k; + } + } + $start = $this->run_log[$lastk]['time']; + $this->run_log[$lastk]['duration'] = microtime( TRUE ) - $start; + $this->run_log[$lastk]['ended'] = true; + + $this->run_log[] = array( 'type' => 'end', + 'tag' => $this->run_log[$lastk]['tag'], + 'index' => $lastk, + 'value' => $name, + 'time' => microtime( TRUE ), + 'duration' => microtime( TRUE ) - $start, + 'parents' => $this->parent_stack, + ); + $this->indent--; + if($this->run_log[$lastk]['duration'] >= $this->threshold){ + $tag_report = ""; + foreach($this->tag_count as $tag=>$count){ + $tag_report .= "$tag: $count, "; + } + if(!empty($tag_report)){ +// $tag_report = "\n$tag_report\n"; + } + $end_txt = sprintf("end: $name - %0.4f seconds $tag_report", $this->run_log[$lastk]['duration'] ); + $this->print_to_console($end_txt, $this->run_log[$lastk]['tag'] , 'end'); + $this->print_to_file($end_txt, $this->run_log[$lastk]['tag'], 'end'); + } + } + + public function increase_tag_count($tag){ + if(!isset($this->tag_count[$tag])){ + $this->tag_count[$tag] = 0; + } + $this->tag_count[$tag]++; + } + + public function get_text(){ + $text = ""; + foreach($this->run_log as $entry){ + $text .= str_repeat(" ",count($entry['parents'])); + if($entry['tag'] != 'text'){ + $text .= $entry['tag'].': '; + } + $text .= $entry['value']; + + if($entry['tag'] == 'end'){ + $text .= sprintf(" - %0.4f seconds", $entry['duration'] ); + } + + $text .= "\n"; + } + return $text; + } + + public function set_file($filename, $tag = 'master'){ + if(!isset($this->file_handle[$tag])){ + $this->file_handles[$tag] = fopen($filename, 'a'); + if(!$this->file_handles[$tag]){ + trigger_error('Could not open file for writing: '.$filename); + } + } + } + + public function note( $msg, $tag = FALSE ) + { + if($tag){ + $this->increase_tag_count($tag); + } + if ( is_array( $msg )) { + $msg = '<pre>' . print_r( $msg, TRUE ) . '</pre>'; + } + $this->debug_messages[] = $msg; + $this->run_log[] = array( 'type' => 'note', + 'tag' => $tag ? $tag:"text", + 'value' => htmlentities($msg), + 'time' => microtime( TRUE ), + 'parents' => $this->parent_stack, + ); + + $this->print_to_file($msg, $tag); + $this->print_to_console($msg, $tag); + + } + + public function print_to_file($msg, $tag = FALSE, $type = FALSE){ + if(!$tag){ + $file_handle_tag = 'master'; + } + else{ + $file_handle_tag = $tag; + } + if($file_handle_tag != 'master' && isset($this->file_handles[$file_handle_tag])){ + $buffer = $this->get_indent(); + $buffer .= "$msg\n"; + if(!empty($this->timestamp)){ + $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer); + } + fwrite($this->file_handles[$file_handle_tag], wordwrap($buffer, $this->max_line_size, "\n ")); + } + if(isset($this->file_handles['master']) && $this->file_handles['master']){ + $buffer = $this->get_indent(); + if($tag){ + $buffer .= "$tag: "; + } + $msg = str_replace("\n","",$msg); + $buffer .= "$msg"; + if(!empty($this->timestamp)){ + $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer); + } + if(strlen($buffer) > $this->max_line_size){ + $buffer = substr($buffer,0,$this->max_line_size - 3)."..."; + } + fwrite($this->file_handles['master'], $buffer."\n"); + } + } + + public function print_to_console($msg, $tag=FALSE){ + if($this->print_to_console){ + if(is_array($this->print_to_console)){ + if(in_array($tag, $this->print_to_console)){ + echo $this->get_indent(); + if($tag){ + echo "$tag: "; + } + echo "$msg\n"; + } + } + else{ + echo $this->get_indent(); + if($tag){ + echo "$tag: "; + } + echo "$msg\n"; + } + } + } + + public function print_totals(){ + $totals = array(); + foreach ( $this->run_log as $k => $entry ) { + if ( $entry['type'] == 'start' && $entry['ended'] == true) { + $totals[$entry['value']]['duration'] += $entry['duration']; + $totals[$entry['value']]['count'] += 1; + } + } + if($this->file_handle){ + foreach($totals as $name=>$details){ + fwrite($this->file_handle,$name.": ".number_format($details['duration'],4)."sec, ".$details['count']." calls \n"); + } + } + } + + private function get_indent(){ + $buf = ""; + for($i = 0; $i < $this->indent; $i++){ + $buf .= " "; + } + return $buf; + } + + + function __destruct(){ + foreach($this->file_handles as $handle){ + fclose($handle); + } + } + +} + +?> diff --git a/plugins/emoticons/emoticons.php b/plugins/emoticons/emoticons.php new file mode 100644 index 000000000..be736b625 --- /dev/null +++ b/plugins/emoticons/emoticons.php @@ -0,0 +1,39 @@ +<?php + +/** + * Display Emoticons + * + * Sample plugin to replace emoticons in plain text message body with real icons + * + * @version 1.0.1 + * @author Thomas Bruederli + * @website http://roundcube.net + */ +class emoticons extends rcube_plugin +{ + public $task = 'mail'; + private $map; + + function init() + { + $this->task = 'mail'; + $this->add_hook('message_part_after', array($this, 'replace')); + + $this->map = array( + ':)' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':)')), + ':-)' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':-)')), + ':(' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':(')), + ':-(' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':-(')), + ); + } + + function replace($args) + { + if ($args['type'] == 'plain') + return array('body' => strtr($args['body'], $this->map)); + + return null; + } + +} + diff --git a/plugins/example_addressbook/example_addressbook.php b/plugins/example_addressbook/example_addressbook.php new file mode 100644 index 000000000..081efcb13 --- /dev/null +++ b/plugins/example_addressbook/example_addressbook.php @@ -0,0 +1,42 @@ +<?php + +/** + * Sample plugin to add a new address book + * with just a static list of contacts + */ +class example_addressbook extends rcube_plugin +{ + private $abook_id = 'static'; + + public function init() + { + $this->add_hook('address_sources', array($this, 'address_sources')); + $this->add_hook('get_address_book', array($this, 'get_address_book')); + + // use this address book for autocompletion queries + // (maybe this should be configurable by the user?) + $config = rcmail::get_instance()->config; + $sources = $config->get('autocomplete_addressbooks', array('sql')); + if (!in_array($this->abook_id, $sources)) { + $sources[] = $this->abook_id; + $config->set('autocomplete_addressbooks', $sources); + } + } + + public function address_sources($p) + { + $p['sources'][$this->abook_id] = array('id' => $this->abook_id, 'name' => 'Static List', 'readonly' => true); + return $p; + } + + public function get_address_book($p) + { + if ($p['id'] == $this->abook_id) { + require_once(dirname(__FILE__) . '/example_addressbook_backend.php'); + $p['instance'] = new example_addressbook_backend; + } + + return $p; + } + +} diff --git a/plugins/example_addressbook/example_addressbook_backend.php b/plugins/example_addressbook/example_addressbook_backend.php new file mode 100644 index 000000000..ad6b89d67 --- /dev/null +++ b/plugins/example_addressbook/example_addressbook_backend.php @@ -0,0 +1,72 @@ +<?php + +/** + * Example backend class for a custom address book + * + * This one just holds a static list of address records + * + * @author Thomas Bruederli + */ +class example_addressbook_backend extends rcube_addressbook +{ + public $primary_key = 'ID'; + public $readonly = true; + + private $filter; + private $result; + + public function __construct() + { + $this->ready = true; + } + + public function set_search_set($filter) + { + $this->filter = $filter; + } + + public function get_search_set() + { + return $this->filter; + } + + public function reset() + { + $this->result = null; + $this->filter = null; + } + + public function list_records($cols=null, $subset=0) + { + $this->result = $this->count(); + $this->result->add(array('ID' => '111', 'name' => "Example Contact", 'firstname' => "Example", 'surname' => "Contact", 'email' => "example@roundcube.net")); + + return $this->result; + } + + public function search($fields, $value, $strict=false, $select=true) + { + // no search implemented, just list all records + return $this->list_records(); + } + + public function count() + { + return new rcube_result_set(1, ($this->list_page-1) * $this->page_size); + } + + public function get_result() + { + return $this->result; + } + + public function get_record($id, $assoc=false) + { + $this->list_records(); + $first = $this->result->first(); + $sql_arr = $first['ID'] == $id ? $first : null; + + return $assoc && $sql_arr ? $sql_arr : $this->result; + } + +} diff --git a/plugins/filesystem_attachments/filesystem_attachments.php b/plugins/filesystem_attachments/filesystem_attachments.php new file mode 100644 index 000000000..9a6c0a81d --- /dev/null +++ b/plugins/filesystem_attachments/filesystem_attachments.php @@ -0,0 +1,144 @@ +<?php +/** + * Filesystem Attachments + * + * This is a core plugin which provides basic, filesystem based + * attachment temporary file handling. This includes storing + * attachments of messages currently being composed, writing attachments + * to disk when drafts with attachments are re-opened and writing + * attachments to disk for inline display in current html compositions. + * + * Developers may wish to extend this class when creating attachment + * handler plugins: + * require_once('plugins/filesystem_attachments/filesystem_attachments.php'); + * class myCustom_attachments extends filesystem_attachments + * + * @author Ziba Scott <ziba@umich.edu> + * @author Thomas Bruederli <roundcube@gmail.com> + * + */ +class filesystem_attachments extends rcube_plugin +{ + public $task = 'mail'; + + function init() + { + // Save a newly uploaded attachment + $this->add_hook('upload_attachment', array($this, 'upload')); + + // Save an attachment from a non-upload source (draft or forward) + $this->add_hook('save_attachment', array($this, 'save')); + + // Remove an attachment from storage + $this->add_hook('remove_attachment', array($this, 'remove')); + + // When composing an html message, image attachments may be shown + $this->add_hook('display_attachment', array($this, 'display')); + + // Get the attachment from storage and place it on disk to be sent + $this->add_hook('get_attachment', array($this, 'get_attachment')); + + // Delete all temp files associated with this user + $this->add_hook('cleanup_attachments', array($this, 'cleanup')); + } + + /** + * Save a newly uploaded attachment + */ + function upload($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + + // use common temp dir for file uploads + // #1484529: we need absolute path on Windows for move_uploaded_file() + $temp_dir = realpath($rcmail->config->get('temp_dir')); + $tmpfname = tempnam($temp_dir, 'rcmAttmnt'); + + if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) { + $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1; + $args['path'] = $tmpfname; + $args['status'] = true; + + // Note the file for later cleanup + $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $tmpfname; + } + + return $args; + } + + /** + * Save an attachment from a non-upload source (draft or forward) + */ + function save($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + $temp_dir = unslashify($rcmail->config->get('temp_dir')); + $tmp_path = tempnam($temp_dir, 'rcmAttmnt'); + + if ($fp = fopen($tmp_path, 'w')) { + fwrite($fp, $args['data']); + fclose($fp); + + $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1; + $args['path'] = $tmp_path; + $args['status'] = true; + + // Note the file for later cleanup + $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $tmp_path; + } + + return $args; + } + + /** + * Remove an attachment from storage + * This is triggered by the remove attachment button on the compose screen + */ + function remove($args) + { + $args['status'] = @unlink($args['path']); + return $args; + } + + /** + * When composing an html message, image attachments may be shown + * For this plugin, the file is already in place, just check for + * the existance of the proper metadata + */ + function display($args) + { + $args['status'] = file_exists($args['path']); + return $args; + } + + /** + * This attachment plugin doesn't require any steps to put the file + * on disk for use. This stub function is kept here to make this + * class handy as a parent class for other plugins which may need it. + */ + function get_attachment($args) + { + return $args; + } + + /** + * Delete all temp files associated with this user + */ + function cleanup($args) + { + // $_SESSION['compose']['attachments'] is not a complete record of + // temporary files because loading a draft or starting a forward copies + // the file to disk, but does not make an entry in that array + if (is_array($_SESSION['plugins']['filesystem_attachments']['tmp_files'])){ + foreach ($_SESSION['plugins']['filesystem_attachments']['tmp_files'] as $filename){ + if(file_exists($filename)){ + unlink($filename); + } + } + unset($_SESSION['plugins']['filesystem_attachments']['tmp_files']); + } + return $args; + } +} diff --git a/plugins/http_authentication/http_authentication.php b/plugins/http_authentication/http_authentication.php new file mode 100644 index 000000000..57422a74d --- /dev/null +++ b/plugins/http_authentication/http_authentication.php @@ -0,0 +1,41 @@ +<?php + +/** + * HTTP Basic Authentication + * + * Make use of an existing HTTP authentication and perform login with the existing user credentials + * + * @version 1.0 + * @author Thomas Bruederli + */ +class http_authentication extends rcube_plugin +{ + + function init() + { + $this->add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function startup($args) + { + // change action to login + if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) + && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) + $args['action'] = 'login'; + + return $args; + } + + function authenticate($args) + { + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $args['user'] = $_SERVER['PHP_AUTH_USER']; + $args['pass'] = $_SERVER['PHP_AUTH_PW']; + } + + return $args; + } + +} + diff --git a/plugins/markasjunk/junk_act.png b/plugins/markasjunk/junk_act.png Binary files differnew file mode 100644 index 000000000..b5a84f604 --- /dev/null +++ b/plugins/markasjunk/junk_act.png diff --git a/plugins/markasjunk/junk_pas.png b/plugins/markasjunk/junk_pas.png Binary files differnew file mode 100644 index 000000000..b88a561a4 --- /dev/null +++ b/plugins/markasjunk/junk_pas.png diff --git a/plugins/markasjunk/localization/en_US.inc b/plugins/markasjunk/localization/en_US.inc new file mode 100644 index 000000000..6f63e161a --- /dev/null +++ b/plugins/markasjunk/localization/en_US.inc @@ -0,0 +1,7 @@ +<?php + +$labels = array(); +$labels['buttontitle'] = 'Mark as Junk'; +$labels['reportedasjunk'] = 'Successfully reported as Junk'; + +?>
\ No newline at end of file diff --git a/plugins/markasjunk/markasjunk.js b/plugins/markasjunk/markasjunk.js new file mode 100644 index 000000000..8b02d7438 --- /dev/null +++ b/plugins/markasjunk/markasjunk.js @@ -0,0 +1,28 @@ +/* Mark-as-Junk plugin script */ + +function rcmail_markasjunk(prop) +{ + if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length)) + return; + + var uids = rcmail.env.uid ? rcmail.env.uid : rcmail.message_list.get_selection().join(','); + + rcmail.set_busy(true, 'loading'); + rcmail.http_post('plugin.markasjunk', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), true); +} + +// callback for app-onload event +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + + // register command (directly enable in message view mode) + rcmail.register_command('plugin.markasjunk', rcmail_markasjunk, rcmail.env.uid); + + // add event-listener to message list + if (rcmail.message_list) + rcmail.message_list.addEventListener('select', function(list){ + rcmail.enable_command('plugin.markasjunk', list.get_selection().length > 0); + }); + }) +} + diff --git a/plugins/markasjunk/markasjunk.php b/plugins/markasjunk/markasjunk.php new file mode 100644 index 000000000..959111d84 --- /dev/null +++ b/plugins/markasjunk/markasjunk.php @@ -0,0 +1,47 @@ +<?php + +/** + * Mark as Junk + * + * Sample plugin that adds a new button to the mailbox toolbar + * to mark the selected messages as Junk and move them to the Junk folder + * + * @version 1.0 + * @author Thomas Bruederli + */ +class markasjunk extends rcube_plugin +{ + public $task = 'mail'; + + function init() + { + $this->register_action('plugin.markasjunk', array($this, 'request_action')); + $GLOBALS['IMAP_FLAGS']['JUNK'] = 'Junk'; + + $rcmail = rcmail::get_instance(); + if ($rcmail->action == '' || $rcmail->action == 'show') { + $this->include_script('markasjunk.js'); + $this->add_texts('localization', true); + $this->add_button(array('command' => 'plugin.markasjunk', 'imagepas' => 'junk_pas.png', 'imageact' => 'junk_act.png'), 'toolbar'); + } + } + + function request_action() + { + $this->add_texts('localization'); + + $uids = get_input_value('_uid', RCUBE_INPUT_POST); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); + + $rcmail = rcmail::get_instance(); + $rcmail->imap->set_flag($uids, 'JUNK'); + + if (($junk_mbox = $rcmail->config->get('junk_mbox')) && $mbox != $junk_mbox) { + $rcmail->output->command('move_messages', $junk_mbox); + } + + $rcmail->output->command('display_message', $this->gettext('reportedasjunk'), 'confirmation'); + $rcmail->output->send(); + } + +}
\ No newline at end of file diff --git a/plugins/new_user_identity/new_user_identity.php b/plugins/new_user_identity/new_user_identity.php new file mode 100644 index 000000000..75595693c --- /dev/null +++ b/plugins/new_user_identity/new_user_identity.php @@ -0,0 +1,49 @@ +<?php +/** + * New user identity + * + * Populates a new user's default identity from LDAP on their first visit. + * + * This plugin requires that a working public_ldap directory be configured. + * + * @version 1.0 + * @author Kris Steinhoff + * + * Example configuration: + * + * // The id of the address book to use to automatically set a new + * // user's full name in their new identity. (This should be an + * // string, which refers to the $rcmail_config['ldap_public'] array.) + * $rcmail_config['new_user_identity_addressbook'] = 'People'; + * + * // When automatically setting a new users's full name in their + * // new identity, match the user's login name against this field. + * $rcmail_config['new_user_identity_match'] = 'uid'; + * + * // Use the value in this field to automatically set a new users's + * // full name in their new identity. + * $rcmail_config['new_user_identity_field'] = 'name'; + */ +class new_user_identity extends rcube_plugin +{ + function init() + { + $this->add_hook('create_user', array($this, 'lookup_user_name')); + } + + function lookup_user_name($args) + { + $rcmail = rcmail::get_instance(); + if ($addressbook = $rcmail->config->get('new_user_identity_addressbook')) { + $match = $rcmail->config->get('new_user_identity_match'); + $ldap = $rcmail->get_address_book($addressbook); + $ldap->prop['search_fields'] = array($match); + $results = $ldap->search($match, $args['user'], TRUE); + if (count($results->records) == 1) { + $args['user_name'] = $results->records[0][$rcmail->config->get('new_user_identity_field')]; + } + } + return $args; + } +} +?> diff --git a/plugins/password/localization/en_US.inc b/plugins/password/localization/en_US.inc new file mode 100644 index 000000000..b54bcd4c9 --- /dev/null +++ b/plugins/password/localization/en_US.inc @@ -0,0 +1,15 @@ +<?php + +$labels = array(); +$labels['changepasswd'] = 'Change Password'; +$labels['curpasswd'] = 'Current Password:'; +$labels['newpasswd'] = 'New Password:'; +$labels['confpasswd'] = 'Confirm New Password:'; + +$messages = array(); +$messages['nopassword'] = "Please input new password."; +$messages['nocurpassword'] = "Please input current password."; +$messages['passwordincorrectly'] = "Current password incorrectly."; +$messages['passwordinconsistency'] = "Inconsistency of password, please try again."; + +?>
\ No newline at end of file diff --git a/plugins/password/localization/pl_PL.inc b/plugins/password/localization/pl_PL.inc new file mode 100644 index 000000000..197999531 --- /dev/null +++ b/plugins/password/localization/pl_PL.inc @@ -0,0 +1,15 @@ +<?php + +$labels = array(); +$labels['changepasswd'] = 'Zmiana hasła'; +$labels['curpasswd'] = 'Aktualne hasło:'; +$labels['newpasswd'] = 'Nowe hasło:'; +$labels['confpasswd'] = 'Potwierdź hasło:'; + +$messages = array(); +$messages['nopassword'] = 'Wprowadź nowe hasło.'; +$messages['nocurpassword'] = 'Wprowadź aktualne hasło.'; +$messages['passwordincorrect'] = 'Błędne aktualne hasło, spróbuj ponownie.'; +$messages['passwordinconsistency'] = 'Hasła nie pasują, spróbuj ponownie.'; + +?> diff --git a/plugins/password/password.js b/plugins/password/password.js new file mode 100644 index 000000000..3d05b622b --- /dev/null +++ b/plugins/password/password.js @@ -0,0 +1,44 @@ +/* Password change interface (tab) */ + +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + // <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span> + var tab = $('<span>').attr('id', 'settingstabpluginpassword').addClass('tablink'); + + var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.password').html(rcmail.gettext('password')).appendTo(tab); + button.bind('click', function(e){ return rcmail.command('plugin.password', this) }); + + // add button and register commands + rcmail.add_element(tab, 'tabs'); + rcmail.register_command('plugin.password', function() { rcmail.goto_url('plugin.password') }, true); + rcmail.register_command('plugin.password-save', function() { + var input_curpasswd = rcube_find_object('_curpasswd'); + var input_newpasswd = rcube_find_object('_newpasswd'); + var input_confpasswd = rcube_find_object('_confpasswd'); + + if (input_curpasswd && input_curpasswd.value=='') { + alert(rcmail.gettext('nocurpassword', 'password')); + input_curpasswd.focus(); + } else if (input_newpasswd && input_newpasswd.value=='') { + alert(rcmail.gettext('nopassword', 'password')); + input_newpasswd.focus(); + } else if (input_confpasswd && input_confpasswd.value=='') { + alert(rcmail.gettext('nopassword', 'password')); + input_confpasswd.focus(); + } else if ((input_newpasswd && input_confpasswd) && (input_newpasswd.value != input_confpasswd.value)) { + alert(rcmail.gettext('passwordinconsistency', 'password')); + input_newpasswd.focus(); + } else { + rcmail.gui_objects.passform.submit(); + } + }, true); + }) + + // set page title + if (rcmail.env.action == 'plugin.password' && rcmail.env.task == 'settings') { + var title = rcmail.gettext('changepasswd','password') + if (rcmail.env.product_name) + title = rcmail.env.product_name + ' :: ' + title; + rcmail.set_pagetitle(title); + } +} diff --git a/plugins/password/password.php b/plugins/password/password.php new file mode 100644 index 000000000..4a35da119 --- /dev/null +++ b/plugins/password/password.php @@ -0,0 +1,160 @@ +<?php + +/** + * Change Password + * + * Sample plugin that adds a possibility to change password + * (Settings -> Password tab) + * + * @version 1.0 + * @author Aleksander 'A.L.E.C' Machniak + */ +class password extends rcube_plugin +{ + public $task = 'settings'; + + function init() + { + $rcmail = rcmail::get_instance(); + // add Tab label + $rcmail->output->add_label('password'); + $this->register_action('plugin.password', array($this, 'password_init')); + $this->register_action('plugin.password-save', array($this, 'password_save')); + $this->register_handler('plugin.body', array($this, 'password_form')); + $this->include_script('password.js'); + } + + function password_init() + { + $this->add_texts('localization/'); + rcmail::get_instance()->output->send('plugin'); + } + + function password_save() + { + $rcmail = rcmail::get_instance(); + + $this->add_texts('localization/'); + + if (!isset($_POST['_curpasswd']) || !isset($_POST['_newpasswd'])) + $rcmail->output->command('display_message', $this->gettext('nopassword'), 'error'); + else { + $curpwd = get_input_value('_curpasswd', RCUBE_INPUT_POST); + $newpwd = get_input_value('_newpasswd', RCUBE_INPUT_POST); + + if ($_SESSION['password'] != $rcmail->encrypt_passwd($curpwd)) + $rcmail->output->command('display_message', $this->gettext('passwordincorrect'), 'error'); + else if ($res = $this->_save($newpwd)) { + $rcmail->output->command('display_message', $this->gettext('successfullysaved'), 'confirmation'); + $_SESSION['password'] = $rcmail->encrypt_passwd($newpwd); + } else + $rcmail->output->command('display_message', $this->gettext('errorsaving'), 'error'); + } + + rcmail_overwrite_action('plugin.password'); + rcmail::get_instance()->output->send('plugin'); + } + + function password_form() + { + $rcmail = rcmail::get_instance(); + + // add some labels to client + $rcmail->output->add_label( + 'password.nopassword', + 'password.nocurpassword', + 'password.passwordinconsistency', + 'password.changepasswd' + ); +// $rcmail->output->set_pagetitle($this->gettext('changepasswd')); + $rcmail->output->set_env('product_name', $rcmail->config->get('product_name')); + + // allow the following attributes to be added to the <table> tag + $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); + + // return the complete edit form as table + $out = '<table' . $attrib_str . ">\n\n"; + + $a_show_cols = array('curpasswd' => array('type' => 'text'), + 'newpasswd' => array('type' => 'text'), + 'confpasswd' => array('type' => 'text')); + + // show current password selection + $field_id = 'curpasswd'; + $input_newpasswd = new html_passwordfield(array('name' => '_curpasswd', 'id' => $field_id, 'size' => 20)); + + $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n", + $field_id, + rep_specialchars_output($this->gettext('curpasswd')), + $input_newpasswd->show($rcmail->config->get('curpasswd'))); + + // show new password selection + $field_id = 'newpasswd'; + $input_newpasswd = new html_passwordfield(array('name' => '_newpasswd', 'id' => $field_id, 'size' => 20)); + + $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n", + $field_id, + rep_specialchars_output($this->gettext('newpasswd')), + $input_newpasswd->show($rcmail->config->get('newpasswd'))); + + // show confirm password selection + $field_id = 'confpasswd'; + $input_confpasswd = new html_passwordfield(array('name' => '_confpasswd', 'id' => $field_id, 'size' => 20)); + + $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n", + $field_id, + rep_specialchars_output($this->gettext('confpasswd')), + $input_confpasswd->show($rcmail->config->get('confpasswd'))); + + $out .= "\n</table>"; + + $out .= '<br />'; + + $out .= $rcmail->output->button(array( + 'command' => 'plugin.password-save', + 'type' => 'input', + 'class' => 'button mainaction', + 'label' => 'save' + )); + + $rcmail->output->add_gui_object('passform', 'password-form'); + + return $rcmail->output->form_tag(array( + 'id' => 'password-form', + 'name' => 'password-form', + 'method' => 'post', + 'action' => './?_task=settings&_action=plugin.password-save', + ), $out); + } + + + private function _save($passwd) + { + $cfg = rcmail::get_instance()->config; + + if (!($sql = $cfg->get('password_query'))) + $sql = "SELECT update_passwd('%p', '%u')"; + + $sql = str_replace('%u', $_SESSION['username'], $sql); + $sql = str_replace('%p', crypt($passwd), $sql); + + if ($dsn = $cfg->get('db_passwd_dsn')) { + $db = new rcube_mdb2($dsn, '', FALSE); + $db->set_debug((bool)$cfg->get('sql_debug')); + $db->db_connect('w'); + } else { + $db = rcmail::get_instance()->get_dbh(); + } + + if (!$db->db_connected) + return false; + + $res = $db->query($sql); + $res = $db->fetch_array($res); + + return $res; + } + +} + +?> diff --git a/plugins/show_additional_headers/show_additional_headers.php b/plugins/show_additional_headers/show_additional_headers.php new file mode 100644 index 000000000..c31c9df6b --- /dev/null +++ b/plugins/show_additional_headers/show_additional_headers.php @@ -0,0 +1,49 @@ +<?php + +/** + * Show additional message headers + * + * Proof-of-concept plugin which will fetch additional headers + * and display them in the message view. + * + * Enable the plugin in config/main.inc.php and add your desired headers: + * $rcmail_config['show_additional_headers'] = array('User-Agent'); + * + * @version 1.0 + * @author Thomas Bruederli + * @website http://roundcube.net + */ +class show_additional_headers extends rcube_plugin +{ + public $task = 'mail'; + + function init() + { + $rcmail = rcmail::get_instance(); + if ($rcmail->action == 'show' || $rcmail->action == 'preview') { + $this->add_hook('imap_init', array($this, 'imap_init')); + $this->add_hook('message_headers_output', array($this, 'message_headers')); + } + } + + function imap_init($p) + { + $rcmail = rcmail::get_instance(); + if ($add_headers = $rcmail->config->get('show_additional_headers', array())) + $p['fetch_headers'] = trim($p['fetch_headers'].' ' . strtoupper(join(' ', $add_headers))); + + return $p; + } + + function message_headers($p) + { + $rcmail = rcmail::get_instance(); + foreach ($rcmail->config->get('show_additional_headers', array()) as $header) { + $key = strtolower($header); + if ($value = $p['headers']->others[$key]) + $p['output'][$key] = array('title' => $header, 'value' => $value); + } + + return $p; + } +} diff --git a/plugins/subscriptions_option/localization/en_US.inc b/plugins/subscriptions_option/localization/en_US.inc new file mode 100644 index 000000000..5a348e0ee --- /dev/null +++ b/plugins/subscriptions_option/localization/en_US.inc @@ -0,0 +1,6 @@ +<?php + +$labels = array(); +$labels['useimapsubscriptions'] = 'Use IMAP Subscriptions'; + +?> diff --git a/plugins/subscriptions_option/subscriptions_option.php b/plugins/subscriptions_option/subscriptions_option.php new file mode 100644 index 000000000..ba7236c67 --- /dev/null +++ b/plugins/subscriptions_option/subscriptions_option.php @@ -0,0 +1,84 @@ +<?php + +/** + * Subscription Options + * + * A plugin which can enable or disable the use of imap subscriptions. + * It includes a toggle on the settings page under "Server Settings". + * The preference can also be locked + * + * Add it to the plugins list in config/main.inc.php to enable the user option + * The user option can be hidden and set globally by adding 'use_subscriptions' + * to the the 'dont_override' configure line: + * $rcmail_config['dont_override'] = array('use_subscriptions'); + * and then set the global preference" + * $rcmail_config['use_subscriptions'] = true; // or false + * + * Roundcube caches folder lists. When a user changes this option or visits + * their folder list, this cache is refreshed. If the option is on the + * 'dont_override' list and the global option has changed, don't expect + * to see the change until the folder list cache is refreshed. + * + * @version 1.0 + * @author Ziba Scott + */ +class subscriptions_option extends rcube_plugin +{ + + function init() + { + $this->add_texts('localization/', false); + $dont_override = rcmail::get_instance()->config->get('dont_override', array()); + if (!in_array('use_subscriptions', $dont_override)){ + $this->add_hook('user_preferences', array($this, 'settings_table')); + $this->add_hook('save_preferences', array($this, 'save_prefs')); + } + $this->add_hook('list_mailboxes', array($this, 'list_mailboxes')); + $this->add_hook('manage_folders', array($this, 'manage_folders')); + } + + function settings_table($args) + { + if ($args['section'] == 'server') { + $use_subscriptions = rcmail::get_instance()->config->get('use_subscriptions'); + $field_id = 'rcmfd_use_subscriptions'; + $use_subscriptions = new html_checkbox(array('name' => '_use_subscriptions', 'id' => $field_id, 'value' => 1)); + + $args['table']->add('title', html::label($field_id, Q($this->gettext('useimapsubscriptions')))); + $args['table']->add(null, $use_subscriptions->show($use_subscriptions?1:0)); + } + + return $args; + } + + function save_prefs($args){ + $rcmail = rcmail::get_instance(); + $use_subscriptions = $rcmail->config->get('use_subscriptions'); + + $args['prefs']['use_subscriptions'] = isset($_POST['_use_subscriptions']) ? true : false; + // if the use_subscriptions preference changes, flush the folder cache + if (($use_subscriptions && !isset($_POST['_use_subscriptions'])) || + (!$use_subscriptions && isset($_POST['_use_subscriptions']))) { + $rcmail->imap_init(true); + $rcmail->imap->clear_cache('mailboxes'); + } + + return $args; + } + + function list_mailboxes($args){ + $rcmail = rcmail::get_instance(); + if (!$rcmail->config->get('use_subscriptions', true)) { + $args['folders'] = iil_C_ListMailboxes($rcmail->imap->conn, $rcmail->imap->_mod_mailbox($args['root']), $args['filter']); + } + return $args; + } + + function manage_folders($args){ + $rcmail = rcmail::get_instance(); + if (!$rcmail->config->get('use_subscriptions', true)) { + $args['table']->remove_column('subscribed'); + } + return $args; + } +} diff --git a/plugins/userinfo/localization/de_CH.inc b/plugins/userinfo/localization/de_CH.inc new file mode 100644 index 000000000..5f236b66c --- /dev/null +++ b/plugins/userinfo/localization/de_CH.inc @@ -0,0 +1,9 @@ +<?php + +$labels = array(); +$labels['userinfo'] = 'Benutzerinfo'; +$labels['created'] = 'Erstellt'; +$labels['lastlogin'] = 'Letztes Login'; +$labels['defaultidentity'] = 'Standard-Absender'; + +?>
\ No newline at end of file diff --git a/plugins/userinfo/localization/en_US.inc b/plugins/userinfo/localization/en_US.inc new file mode 100644 index 000000000..1a2fd9016 --- /dev/null +++ b/plugins/userinfo/localization/en_US.inc @@ -0,0 +1,9 @@ +<?php + +$labels = array(); +$labels['userinfo'] = 'User info'; +$labels['created'] = 'Created'; +$labels['lastlogin'] = 'Last Login'; +$labels['defaultidentity'] = 'Default Identity'; + +?>
\ No newline at end of file diff --git a/plugins/userinfo/userinfo.js b/plugins/userinfo/userinfo.js new file mode 100644 index 000000000..70a5085b3 --- /dev/null +++ b/plugins/userinfo/userinfo.js @@ -0,0 +1,16 @@ +/* Show user-info plugin script */ + +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + // <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span> + var tab = $('<span>').attr('id', 'settingstabpluginuserinfo').addClass('tablink'); + + var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.userinfo').html(rcmail.gettext('userinfo', 'userinfo')).appendTo(tab); + button.bind('click', function(e){ return rcmail.command('plugin.userinfo', this) }); + + // add button and register command + rcmail.add_element(tab, 'tabs'); + rcmail.register_command('plugin.userinfo', function(){ rcmail.goto_url('plugin.userinfo') }, true); + }) +} + diff --git a/plugins/userinfo/userinfo.php b/plugins/userinfo/userinfo.php new file mode 100644 index 000000000..0f1b18cd9 --- /dev/null +++ b/plugins/userinfo/userinfo.php @@ -0,0 +1,53 @@ +<?php + +/** + * Sample plugin that adds a new tab to the settings section + * to display some information about the current user + */ +class userinfo extends rcube_plugin +{ + public $task = 'settings'; + + function init() + { + $this->add_texts('localization/', array('userinfo')); + $this->register_action('plugin.userinfo', array($this, 'infostep')); + $this->include_script('userinfo.js'); + } + + function infostep() + { + $this->register_handler('plugin.body', array($this, 'infohtml')); + rcmail::get_instance()->output->send('plugin'); + } + + function infohtml() + { + $rcmail = rcmail::get_instance(); + $user = $rcmail->user; + + $table = new html_table(array('cols' => 2, 'cellpadding' => 3)); + + $table->add('title', 'ID'); + $table->add('', Q($user->ID)); + + $table->add('title', Q($this->gettext('username'))); + $table->add('', Q($user->data['username'])); + + $table->add('title', Q($this->gettext('server'))); + $table->add('', Q($user->data['mail_host'])); + + $table->add('title', Q($this->gettext('created'))); + $table->add('', Q($user->data['created'])); + + $table->add('title', Q($this->gettext('lastlogin'))); + $table->add('', Q($user->data['last_login'])); + + $identity = $user->get_identity(); + $table->add('title', Q($this->gettext('defaultidentity'))); + $table->add('', Q($identity['name'] . ' <' . $identity['email'] . '>')); + + return html::tag('h4', null, Q('Infos for ' . $user->get_username())) . $table->show(); + } + +}
\ No newline at end of file diff --git a/plugins/vcard_attachments/vcard_attachments.php b/plugins/vcard_attachments/vcard_attachments.php new file mode 100644 index 000000000..da8ea1c15 --- /dev/null +++ b/plugins/vcard_attachments/vcard_attachments.php @@ -0,0 +1,115 @@ +<?php + +/** + * Detect VCard attachments and show a button to add them to address book + * + * @version 1.0 + * @author Thomas Bruederli + */ +class vcard_attachments extends rcube_plugin +{ + public $task = 'mail'; + + private $message; + private $vcard_part; + + function init() + { + $rcmail = rcmail::get_instance(); + if ($rcmail->action == 'show' || $rcmail->action == 'preview') { + $this->add_hook('message_load', array($this, 'message_load')); + $this->add_hook('template_object_messagebody', array($this, 'html_output')); + } + + $this->register_action('plugin.savevcard', array($this, 'save_vcard')); + } + + /** + * Check message attachments for vcards + */ + function message_load($p) + { + $this->message = $p['object']; + + foreach ((array)$this->message->attachments as $attachment) { + if (in_array($attachment->mimetype, array('text/vcard', 'text/x-vcard'))) + $this->vcard_part = $attachment->mime_id; + } + } + + /** + * This callback function adds a box below the message content + * if there is a vcard attachment available + */ + function html_output($p) + { + if ($this->vcard_part) { + $vcard = new rcube_vcard($this->message->get_part_content($this->vcard_part)); + + // successfully parsed vcard + if ($vcard->displayname) { + $display = $vcard->displayname; + if ($vcard->email[0]) + $display .= ' <'.$vcard->email[0].'>'; + + // add box below messsage body + $p['content'] .= html::p(array('style' => "margin:1em; padding:0.5em; border:1px solid #999; width: auto;"), + html::a(array( + 'href' => "#", + 'onclick' => "return plugin_vcard_save_contact('".JQ($this->vcard_part)."')", + 'title' => "Save contact in local address book"), // TODO: localize this title + html::img(array('src' => '/images/buttons/add_contact_act.png', 'align' => "middle"))) + . ' ' . html::span(null, Q($display))); + + $this->include_script('vcardattach.js'); + } + } + + return $p; + } + + /** + * Handler for request action + */ + function save_vcard() + { + $uid = get_input_value('_uid', RCUBE_INPUT_POST); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); + $mime_id = get_input_value('_part', RCUBE_INPUT_POST); + + $rcmail = rcmail::get_instance(); + $part = $uid && $mime_id ? $rcmail->imap->get_message_part($uid, $mime_id) : null; + + $error_msg = 'Failed to saved vcard'; // TODO: localize this text + + if ($part && ($vcard = new rcube_vcard($part)) && $vcard->displayname && $vcard->email) { + $contacts = $rcmail->get_address_book(null, true); + + // check for existing contacts + $existing = $contacts->search('email', $vcard->email[0], true, false); + if ($done = $existing->count) { + $rcmail->output->command('display_message', $this->gettext('contactexists'), 'warning'); + } + else { + // add contact + $success = $contacts->insert(array( + 'name' => $vcard->displayname, + 'firstname' => $vcard->firstname, + 'surname' => $vcard->surname, + 'email' => $vcard->email[0], + 'vcard' => $vcard->export(), + )); + + if ($success) + $rcmail->output->command('display_message', $this->gettext('addedsuccessfully'), 'confirmation'); + else + $rcmail->output->command('display_message', $error_msg, 'error'); + } + } + else + $rcmail->output->command('display_message', $error_msg, 'error'); + + $rcmail->output->send(); + } + +}
\ No newline at end of file diff --git a/plugins/vcard_attachments/vcardattach.js b/plugins/vcard_attachments/vcardattach.js new file mode 100644 index 000000000..e03e5084d --- /dev/null +++ b/plugins/vcard_attachments/vcardattach.js @@ -0,0 +1,10 @@ + +function plugin_vcard_save_contact(mime_id) +{ + rcmail.set_busy(true, 'loading'); + rcmail.http_post('plugin.savevcard', '_uid='+rcmail.env.uid+'&_mbox='+urlencode(rcmail.env.mailbox)+'&_part='+urlencode(mime_id), true); + + return false; +} + + diff --git a/program/include/html.php b/program/include/html.php index 01ad41544..78e696cb6 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -33,7 +33,7 @@ class html 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 @@ class html_table extends html $this->header[] = $cell; } + /** + * Remove a column from a table + * Useful for plugins making alterations + * + * @param string $class + */ + public function remove_column($class) + { + // Remove the header + foreach($this->header as $index=>$header){ + if($header->attrib['class'] == $class){ + unset($this->header[$index]); + break; + } + } + + // Remove cells from rows + foreach($this->rows as $i=>$row){ + foreach($row->cells as $j=>$cell){ + if($cell->attrib['class'] == $class){ + unset($this->rows[$i]->cells[$j]); + break; + } + } + } + } + + /** * Jump to next row * diff --git a/program/include/iniset.php b/program/include/iniset.php index 234f9ebcb..46f3750ea 100755 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -22,7 +22,7 @@ // application constants -define('RCMAIL_VERSION', '0.2-trunk'); +define('RCMAIL_VERSION', '0.3-trunk'); define('RCMAIL_CHARSET', 'UTF-8'); define('JS_OBJECT_NAME', 'rcmail'); diff --git a/program/include/main.inc b/program/include/main.inc index b22be1aca..b3d0dab2a 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -88,9 +88,9 @@ function get_sequence_name($sequence) * @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 rcube_charset_convert($str, $from, $to=NULL) */ 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 @@ function parse_attrib_string($str) 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 format_email_recipient($email, $name='') */ function console() { + $args = func_get_args(); + if (class_exists('rcmail', false)) + rcmail::get_instance()->plugins->exec_hook('console', $args); + $msg = array(); - foreach (func_get_args() as $arg) - $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; + foreach ($args as $arg) + $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; if (!($GLOBALS['CONFIG']['debug_level'] & 4)) write_log('console', join(";\n", $msg)); diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 9aad25b27..56fc2f5db 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -37,6 +37,7 @@ class rcmail public $db; public $imap; public $output; + public $plugins; public $task = 'mail'; public $action = ''; public $comm_path = './'; @@ -88,7 +89,7 @@ class rcmail $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 @@ class rcmail // create IMAP object if ($this->task == 'mail') $this->imap_init(); + + // create plugin API and load plugins + $this->plugins = rcube_plugin_api::get_instance(); } @@ -255,10 +259,19 @@ class rcmail $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. foreach ($ldap_config as $id => $prop) { @@ -598,7 +611,7 @@ class rcmail * @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 @@ class rcmail $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 @@ class rcmail * * @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 @@ class rcmail $_SESSION['language'] = $lang; } + + // append additional texts (from plugin) + if (is_array($add) && !empty($add)) + $this->texts += $add; } @@ -920,18 +940,20 @@ class rcmail { if (!is_array($p)) $p = array('_action' => @func_get_arg(0)); + + $task = $p['_task'] ? $p['_task'] : $p['task']; + if (!$task || !in_array($task, rcmail::$main_tasks)) + $task = $this->task; - if (!$p['task'] || !in_array($p['task'], rcmail::$main_tasks)) - $p['task'] = $this->task; - - $p['_task'] = $p['task']; + $p['_task'] = $task; unset($p['task']); $url = './'; $delm = '?'; - foreach (array_reverse($p) as $par => $val) + foreach (array_reverse($p) as $key => $val) { if (!empty($val)) { + $par = $key[0] == '_' ? $key : '_'.$key; $url .= $delm.urlencode($par).'='.urlencode($val); $delm = '&'; } diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php new file mode 100644 index 000000000..9e970f213 --- /dev/null +++ b/program/include/rcube_addressbook.php @@ -0,0 +1,169 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_addressbook.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2006-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Interface to the local address book database | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + + +/** + * Abstract skeleton of an address book/repository + * + * @package Addressbook + */ +abstract class rcube_addressbook +{ + /** public properties */ + var $primary_key; + var $readonly = true; + var $ready = false; + var $list_page = 1; + var $page_size = 10; + + /** + * Save a search string for future listings + * + * @param mixed Search params to use in listing method, obtained by get_search_set() + */ + abstract function set_search_set($filter); + + /** + * Getter for saved search properties + * + * @return mixed Search properties used by this class + */ + abstract function get_search_set(); + + /** + * Reset saved results and search parameters + */ + abstract function reset(); + + /** + * List the current set of contact records + * + * @param array List of cols to show + * @param int Only return this number of records, use negative values for tail + * @return array Indexed list of contact records, each a hash array + */ + abstract function list_records($cols=null, $subset=0); + + /** + * Search records + * + * @param array List of fields to search in + * @param string Search value + * @param boolean True if results are requested, False if count only + * @return Indexed list of contact records and 'count' value + */ + abstract function search($fields, $value, $strict=false, $select=true); + + /** + * Count number of available contacts in database + * + * @return object rcube_result_set Result set with values for 'count' and 'first' + */ + abstract function count(); + + /** + * Return the last result set + * + * @return object rcube_result_set Current result set or NULL if nothing selected yet + */ + abstract function get_result(); + + /** + * Get a specific contact record + * + * @param mixed record identifier(s) + * @param boolean True to return record as associative array, otherwise a result set is returned + * @return mixed Result object with all record fields or False if not found + */ + abstract function get_record($id, $assoc=false); + + /** + * Close connection to source + * Called on script shutdown + */ + function close() { } + + /** + * Set internal list page + * + * @param number Page number to list + * @access public + */ + function set_page($page) + { + $this->list_page = (int)$page; + } + + /** + * Set internal page size + * + * @param number Number of messages to display on one page + * @access public + */ + function set_pagesize($size) + { + $this->page_size = (int)$size; + } + + /** + * Create a new contact record + * + * @param array Assoziative array with save data + * @param boolean True to check for duplicates first + * @return The created record ID on success, False on error + */ + function insert($save_data, $check=false) + { + /* empty for read-only address books */ + } + + /** + * Update a specific contact record + * + * @param mixed Record identifier + * @param array Assoziative array with save data + * @return True on success, False on error + */ + function update($id, $save_cols) + { + /* empty for read-only address books */ + } + + /** + * Mark one or more contact records as deleted + * + * @param array Record identifiers + */ + function delete($ids) + { + /* empty for read-only address books */ + } + + /** + * Remove all records from the database + */ + function delete_all() + { + /* empty for read-only address books */ + } + +} +
\ No newline at end of file diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 7be2b0d2c..1312a73de 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -74,6 +74,7 @@ class rcube_config // fix paths $this->prop['log_dir'] = $this->prop['log_dir'] ? unslashify($this->prop['log_dir']) : INSTALL_PATH . 'logs'; $this->prop['temp_dir'] = $this->prop['temp_dir'] ? unslashify($this->prop['temp_dir']) : INSTALL_PATH . 'temp'; + $this->prop['plugins_dir'] = $this->prop['plugins_dir'] ? unslashify($this->prop['plugins_dir']) : INSTALL_PATH . 'plugins'; // fix default imap folders encoding foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php index 65d89ca2b..f440e5f8a 100644 --- a/program/include/rcube_contacts.php +++ b/program/include/rcube_contacts.php @@ -25,7 +25,7 @@ * * @package Addressbook */ -class rcube_contacts +class rcube_contacts extends rcube_addressbook { var $db = null; var $db_name = ''; @@ -60,30 +60,6 @@ class rcube_contacts /** - * Set internal list page - * - * @param number Page number to list - * @access public - */ - function set_page($page) - { - $this->list_page = (int)$page; - } - - - /** - * Set internal page size - * - * @param number Number of messages to display on one page - * @access public - */ - function set_pagesize($size) - { - $this->page_size = (int)$size; - } - - - /** * Save a search string for future listings * * @param string SQL params to use in listing method @@ -118,13 +94,6 @@ class rcube_contacts /** - * Close connection to source - * Called on script shutdown - */ - function close(){} - - - /** * List the current set of contact records * * @param array List of cols to show @@ -233,7 +202,7 @@ class rcube_contacts * * @return Result array or NULL if nothing selected yet */ - function get_result($as_res=true) + function get_result() { return $this->result; } diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php index 78f6176bf..c83c6aeea 100644 --- a/program/include/rcube_html_page.php +++ b/program/include/rcube_html_page.php @@ -31,8 +31,8 @@ class rcube_html_page 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 @@ class rcube_html_page 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 @@ class rcube_html_page // 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 @@ class rcube_html_page if (is_array($this->script_files['foot'])) { foreach ($this->script_files['foot'] as $file) { - $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file); + $__page_footer .= sprintf($this->script_tag_file, $file); } } diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index da7c5bf88..e2b6c0d9a 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -55,6 +55,7 @@ class rcube_imap 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 @@ class rcube_imap 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 @@ class rcube_imap $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 @@ class rcube_imap 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 @@ class rcube_imap // 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 @@ class rcube_imap 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 @@ class rcube_imap 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 @@ class rcube_imap // featch headers if unserialize failed if (empty($this->cache[$cache_key][$uid])) - $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true); + $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); } } diff --git a/program/include/rcube_json_output.php b/program/include/rcube_json_output.php index 0bd3a2bad..a14f4ae14 100644 --- a/program/include/rcube_json_output.php +++ b/program/include/rcube_json_output.php @@ -196,26 +196,33 @@ class rcube_json_output * @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']); + $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; + // send response code - echo $this->get_js_commands() . $add; + $response['exec'] = $this->get_js_commands() . $add; - // flush the output buffer - if ($flush) - flush(); + echo json_serialize($response); } @@ -227,14 +234,7 @@ class rcube_json_output private function get_js_commands() { $out = ''; - - if (sizeof($this->env)) - $out .= 'this.set_env('.json_serialize($this->env).");\n"; - foreach($this->texts as $name => $text) { - $out .= sprintf("this.add_label('%s', '%s');\n", $name, JQ($text)); - } - foreach ($this->commands as $i => $args) { $method = array_shift($args); foreach ($args as $i => $arg) { diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index 016fe94d5..544c7f744 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -24,7 +24,7 @@ * * @package Addressbook */ -class rcube_ldap +class rcube_ldap extends rcube_addressbook { var $conn; var $prop = array(); diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index ec3be4b00..5c03b2147 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -84,6 +84,9 @@ class rcube_message else { $this->body = $this->imap->get_body($uid); } + + // notify plugins and let them analyze this structured message object + $this->app->plugins->exec_hook('message_load', array('object' => $this)); } diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php new file mode 100644 index 000000000..62f65a9e4 --- /dev/null +++ b/program/include/rcube_plugin.php @@ -0,0 +1,196 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_plugin.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Abstract plugins interface/class | + | All plugins need to extend this class | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * Plugin interface class + * + * @package Core + */ +abstract class rcube_plugin +{ + public $ID; + public $api; + public $task; + protected $home; + protected $urlbase; + + /** + * Default constructor. + */ + public function __construct($api) + { + $this->ID = get_class($this); + $this->api = $api; + $this->home = $api->dir . DIRECTORY_SEPARATOR . $this->ID; + $this->urlbase = $api->url . $this->ID . '/'; + } + + /** + * Initialization method, needs to be implemented by the plugin itself + */ + abstract function init(); + + /** + * Register a callback function for a specific (server-side) hook + * + * @param string Hook name + * @param mixed Callback function as string or array with object reference and method name + */ + public function add_hook($hook, $callback) + { + $this->api->register_hook($hook, $callback); + } + + /** + * Load localized texts from the plugins dir + * + * @param string Directory to search in + * @param mixed Make texts also available on the client (array with list or true for all) + */ + public function add_texts($dir, $add2client = false) + { + $domain = $this->ID; + + $lang = $_SESSION['language']; + $locdir = slashify(realpath(slashify($this->home) . $dir)); + $texts = array(); + + foreach (array('en_US', $lang) as $lng) { + @include($locdir . $lng . '.inc'); + $texts = (array)$labels + (array)$messages + (array)$texts; + } + + // prepend domain to text keys and add to the application texts repository + if (!empty($texts)) { + $add = array(); + foreach ($texts as $key => $value) + $add[$domain.'.'.$key] = $value; + + $rcmail = rcmail::get_instance(); + $rcmail->load_language($lang, $add); + + // add labels to client + if ($add2client) { + $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add); + $rcmail->output->add_label($js_labels); + } + } + } + + /** + * Wrapper for rcmail::gettext() adding the plugin ID as domain + * + * @return string Localized text + * @see rcmail::gettext() + */ + function gettext($p) + { + return rcmail::get_instance()->gettext($p, $this->ID); + } + + /** + * Register a handler for a specific client-request action + * + * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction + * + * @param string Action name (should be unique) + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_action($action, $callback) + { + $this->api->register_action($action, $this->ID, $callback); + } + + /** + * Register a handler function for a template object + * + * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" /> + * will be replaced by the return value if the registered callback function. + * + * @param string Object name (should be unique and start with 'plugin.') + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_handler($name, $callback) + { + $this->api->register_handler($name, $this->ID, $callback); + } + + /** + * Make this javascipt file available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_script($fn) + { + $this->api->include_script($this->ressource_url($fn)); + } + + /** + * Make this stylesheet available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_stylesheet($fn) + { + $this->api->include_stylesheet($this->ressource_url($fn)); + } + + /** + * Append a button to a certain container + * + * @param array Hash array with named parameters (as used in skin templates) + * @param string Container name where the buttons should be added to + * @see rcube_remplate::button() + */ + public function add_button($p, $container) + { + if ($this->api->output->type == 'html') { + // fix relative paths + foreach (array('imagepas', 'imageact', 'imagesel') as $key) + if ($p[$key]) + $p[$key] = $this->api->url . $this->ressource_url($p[$key]); + + $this->api->add_content($this->api->output->button($p), $container); + } + } + + /** + * Make the given file name link into the plugin directory + */ + private function ressource_url($fn) + { + if ($fn[0] != '/' && !eregi('^https?://', $fn)) + return $this->ID . '/' . $fn; + else + return $fn; + } + + /** + * Callback function for array_map + */ + private function label_map_callback($key) + { + return $this->ID.'.'.$key; + } + + +} + diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php new file mode 100644 index 000000000..4780f2e7e --- /dev/null +++ b/program/include/rcube_plugin_api.php @@ -0,0 +1,312 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_plugin_api.php | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Plugins repository | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * The plugin loader and global API + * + * @package Core + */ +class rcube_plugin_api +{ + static private $instance; + + public $dir; + public $url = 'plugins/'; + public $output; + + public $handlers = array(); + private $plugins = array(); + private $actions = array(); + private $actionmap = array(); + private $objectsmap = array(); + private $template_contents = array(); + + private $required_plugins = array('filesystem_attachments'); + + /** + * This implements the 'singleton' design pattern + * + * @return object rcube_plugin_api The one and only instance if this class + */ + static function get_instance() + { + if (!self::$instance) { + self::$instance = new rcube_plugin_api(); + } + + return self::$instance; + } + + + /** + * Private constructor + */ + private function __construct() + { + $rcmail = rcmail::get_instance(); + $this->dir = realpath($rcmail->config->get('plugins_dir')); + } + + + /** + * Load and init all enabled plugins + * + * This has to be done after rcmail::load_gui() or rcmail::init_json() + * was called because plugins need to have access to rcmail->output + */ + public function init() + { + $rcmail = rcmail::get_instance(); + $this->output = $rcmail->output; + + $plugins_dir = dir($this->dir); + $plugins_enabled = (array)$rcmail->config->get('plugins', array()); + + foreach ($plugins_enabled as $plugin_name) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + + if (file_exists($fn)) { + include($fn); + + // instantiate class if exists + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance and task specification + if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || $plugin->task == $rcmail->task)) { + $plugin->init(); + $this->plugins[] = $plugin; + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false); + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false); + } + } + + // check existance of all required core plugins + foreach ($this->required_plugins as $plugin_name) { + $loaded = false; + foreach ($this->plugins as $plugin) { + if ($plugin instanceof $plugin_name) { + $loaded = true; + break; + } + } + + // load required core plugin if no derivate was found + if (!$loaded) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + if (file_exists($fn)) { + include($fn); + + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance + if (is_subclass_of($plugin, 'rcube_plugin')) { + $plugin->init(); + $this->plugins[] = $plugin; + $loaded = true; + } + } + } + } + + // trigger fatal error if still not loaded + if (!$loaded) { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true); + } + } + + // register an internal hook + $this->register_hook('template_container', array($this, 'template_container_hook')); + + // maybe also register a shudown function which triggers shutdown functions of all plugin objects + + + // call imap_init right now + // (should actually be done in rcmail::imap_init() but plugins are not initialized then) + if ($rcmail->imap) { + $hook = $this->exec_hook('imap_init', array('fetch_headers' => $rcmail->imap->fetch_add_headers)); + if ($hook['fetch_headers']) + $rcmail->imap->fetch_add_headers = $hook['fetch_headers']; + } + } + + + /** + * Allows a plugin object to register a callback for a certain hook + * + * @param string Hook name + * @param mixed String with global function name or array($obj, 'methodname') + */ + public function register_hook($hook, $callback) + { + if (is_callable($callback)) + $this->handlers[$hook][] = $callback; + else + raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false); + } + + + /** + * Triggers a plugin hook. + * This is called from the application and executes all registered handlers + * + * @param string Hook name + * @param array Named arguments (key->value pairs) + * @return array The (probably) altered hook arguments + */ + public function exec_hook($hook, $args = array()) + { + $args += array('abort' => false); + + foreach ((array)$this->handlers[$hook] as $callback) { + $ret = call_user_func($callback, $args); + if ($ret && is_array($ret)) + $args = $ret + $args; + + if ($args['abort']) + break; + } + + return $args; + } + + + /** + * Let a plugin register a handler for a specific request + * + * @param string Action name (_task=mail&_action=plugin.foo) + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_action($action, $owner, $callback) + { + // check action name + if (strpos($action, 'plugin.') !== 0) + $action = 'plugin.'.$action; + + // can register action only if it's not taken or registered by myself + if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) { + $this->actions[$action] = $callback; + $this->actionmap[$action] = $owner; + } + else { + raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false); + } + } + + + /** + * This method handles requests like _task=mail&_action=plugin.foo + * It executes the callback function that was registered with the given action. + * + * @param string Action name + */ + public function exec_action($action) + { + if (isset($this->actions[$action])) { + call_user_func($this->actions[$action]); + } + else { + raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true); + } + } + + + /** + * Register a handler function for template objects + * + * @param string Object name + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_handler($name, $owner, $callback) + { + // check name + if (strpos($name, 'plugin.') !== 0) + $name = 'plugin.'.$name; + + // can register handler only if it's not taken or registered by myself + if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) { + $this->output->add_handler($name, $callback); + $this->objectsmap[$name] = $owner; + } + else { + raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false); + } + } + + /** + * Include a plugin script file in the current HTML page + */ + public function include_script($fn) + { + if ($this->output->type == 'html') { + $src = $this->ressource_url($fn); + $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); + } + } + + /** + * Include a plugin stylesheet in the current HTML page + */ + public function include_stylesheet($fn) + { + if ($this->output->type == 'html') { + $src = $this->ressource_url($fn); + $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src))); + } + } + + /** + * Save the given HTML content to be added to a template container + */ + public function add_content($html, $container) + { + $this->template_contents[$container] .= $html . "\n"; + } + + /** + * Callback for template_container hooks + */ + private function template_container_hook($attrib) + { + $container = $attrib['name']; + return array('content' => $this->template_contents[$container]); + } + + /** + * Make the given file name link into the plugins directory + */ + private function ressource_url($fn) + { + if ($fn[0] != '/' && !eregi('^https?://', $fn)) + return $this->url . $fn; + else + return $fn; + } + +} + diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php index 1e732ca11..557509a14 100755 --- a/program/include/rcube_template.php +++ b/program/include/rcube_template.php @@ -66,11 +66,12 @@ class rcube_template extends rcube_html_page $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 @@ class rcube_template extends rcube_html_page */ 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 @@ class rcube_template extends rcube_html_page $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 @@ class rcube_template extends rcube_html_page */ private function parse_xml($input) { - return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command_callback'), $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); + return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input); } /** - * 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 @@ class rcube_template extends rcube_html_page $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 @@ class rcube_template extends rcube_html_page 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 @@ class rcube_template extends rcube_html_page $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 @@ class rcube_template extends rcube_html_page $out = $input_action->show(); $out .= $input_tzone->show(); + $out .= $input_url->show(); $out .= $table->show(); // surround html output with a form tag diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php index 17debeb7a..b68c56cfa 100644 --- a/program/include/rcube_user.php +++ b/program/include/rcube_user.php @@ -346,16 +346,22 @@ class rcube_user */ 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 @@ class rcube_user 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 @@ class rcube_user (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 @@ class rcube_user 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 @@ class rcube_user { $result[] = trim(str_replace('\\@', '@', $arr[0])); - if ($first) + if ($first) return $result[0]; } } diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php index ce5087a0f..444900cde 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -263,9 +263,9 @@ class rcube_vcard $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop); } - if (!preg_match('/^(BEGIN|END)$/', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { + if (!preg_match('/^(BEGIN|END)$/i', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) { $entry = array(''); - $field = $regs2[1][0]; + $field = strtoupper($regs2[1][0]); foreach($regs2[1] as $attrid => $attr) { if ((list($key, $value) = explode('=', $attr)) && $value) { diff --git a/program/js/app.js b/program/js/app.js index 205bb2d6d..0e0c8bf25 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -10,27 +10,26 @@ | Authors: Thomas Bruederli <roundcube@gmail.com> | | Charles McNulty <charles@charlesmcnulty.com> | +-----------------------------------------------------------------------+ - | Requires: common.js, list.js | + | Requires: jquery.js, common.js, list.js | +-----------------------------------------------------------------------+ $Id$ */ -var rcube_webmail_client; - function rcube_webmail() - { +{ this.env = new Object(); this.labels = new Object(); this.buttons = new Object(); this.gui_objects = new Object(); + this.gui_containers = new Object(); this.commands = new Object(); + this.command_handlers = new Object(); this.onloads = new Array(); // create protected reference to myself - rcube_webmail_client = this; - this.ref = 'rcube_webmail_client'; + this.ref = 'rcmail'; var ref = this; // webmail client settings @@ -53,6 +52,12 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() { 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 @@ function rcube_webmail() } else this.contact_list.focus(); + + this.gui_objects.folderlist = this.gui_objects.contactslist; } this.set_page_buttons(); @@ -316,20 +347,16 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() case 'logout': this.goto_url('logout', '', true); - break; + break; // commands to switch task case 'mail': @@ -558,7 +601,6 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() break; } + + this.triggerEvent('after'+command, props); return obj ? false : true; }; @@ -1114,13 +1156,18 @@ function rcube_webmail() }; // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ -1309,10 +1354,6 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() { 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() } // 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 @@ function rcube_webmail() } // 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 @@ function rcube_webmail() } 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 (editor = tinyMCE.get('compose-body')) + if (value_to) + str += value_to+':'; + if (value_cc) + str += value_cc+':'; + if (value_bcc) + str += value_bcc+':'; + if (value_subject) + str += value_subject+':'; + + 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() { // 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 @@ function rcube_webmail() } } - if (input_message) - input_message.value = message; + input_message.val(message); this.env.identity = id; return true; @@ -2256,27 +2294,24 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() } // 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 @@ function rcube_webmail() this.ksearch_selected = null; if (this.ksearch_pane) - this.ksearch_pane.show(0); + this.ksearch_pane.hide(); }; @@ -2788,19 +2814,18 @@ function rcube_webmail() // 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 @@ function rcube_webmail() (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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() } }; - // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() { 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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="" />' : ' '; + col.html(attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : ' '); 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() { // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() // 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 - 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); + console.log('HTTP POST: ' + url); + jQuery.get(url, { _unlock:(lock?1:0) }, function(data){ ref.http_response(data); }, 'json'); + }; - if (lock) - this.set_busy(true); + // 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' : ''); - 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); - } - }; + // send request + 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); - // 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()); + // 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 (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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() 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 @@ function rcube_webmail() } }; - } // end object rcube_webmail +} // end object rcube_webmail -/** - * Class for sending HTTP requests - * @constructor - */ -function rcube_http_request() - { - this.url = ''; - this.busy = false; - this.xmlhttp = null; - - // reset object properties - this.reset = function() - { - // set unassigned event handlers - this.onloading = function(){ }; - this.onloaded = function(){ }; - this.oninteractive = function(){ }; - this.oncomplete = function(){ }; - this.onabort = function(){ }; - this.onerror = function(){ }; - - this.url = ''; - this.busy = false; - this.xmlhttp = null; - } - - // create HTMLHTTP object - this.build = function() - { - if (window.XMLHttpRequest) - this.xmlhttp = new XMLHttpRequest(); - else if (window.ActiveXObject) - { - try { this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } - catch(e) { this.xmlhttp = null; } - } - else - { - - } - } - - // send GET request - this.GET = function(url) - { - this.build(); - - if (!this.xmlhttp) - { - this.onerror(this); - return false; - } - - var _ref = this; - this.url = url; - this.busy = true; - - this.xmlhttp.onreadystatechange = function(){ _ref.xmlhttp_onreadystatechange(); }; - this.xmlhttp.open('GET', url, true); - this.xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid')); - this.xmlhttp.send(null); - }; - - this.POST = function(url, body, contentType) - { - // default value for contentType if not provided - if (typeof(contentType) == 'undefined') - contentType = 'application/x-www-form-urlencoded'; - - this.build(); - - if (!this.xmlhttp) - { - this.onerror(this); - return false; - } - - var req_body = body; - if (typeof(body) == 'object') - { - req_body = ''; - for (var p in body) - req_body += (req_body ? '&' : '') + p+'='+urlencode(body[p]); - } - - var ref = this; - this.url = url; - this.busy = true; - - this.xmlhttp.onreadystatechange = function() { ref.xmlhttp_onreadystatechange(); }; - this.xmlhttp.open('POST', url, true); - this.xmlhttp.setRequestHeader('Content-Type', contentType); - this.xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid')); - this.xmlhttp.send(req_body); - }; - - // handle onreadystatechange event - this.xmlhttp_onreadystatechange = function() - { - if(this.xmlhttp.readyState == 1) - this.onloading(this); - - else if(this.xmlhttp.readyState == 2) - this.onloaded(this); - - else if(this.xmlhttp.readyState == 3) - this.oninteractive(this); - - else if(this.xmlhttp.readyState == 4) - { - try { - if (this.xmlhttp.status == 0) - this.onabort(this); - else if(this.xmlhttp.status == 200) - this.oncomplete(this); - else - this.onerror(this); - - this.busy = false; - } - catch(err) - { - this.onerror(this); - this.busy = false; - } - } - } - - // getter method for HTTP headers - this.get_header = function(name) - { - return this.xmlhttp.getResponseHeader(name); - }; - - this.get_text = function() - { - return this.xmlhttp.responseText; - }; - - this.get_xml = function() - { - return this.xmlhttp.responseXML; - }; - - this.reset(); - - } // end class rcube_http_request - -// helper function to call the init method with a delay -function call_init(o) - { - window.setTimeout('if (window[\''+o+'\'] && window[\''+o+'\'].init) { '+o+'.init(); }', - bw.win ? 500 : 200); - } +// copy event engine prototype +rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; +rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; +rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; diff --git a/program/js/common.js b/program/js/common.js index bd699d924..407da4170 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -93,7 +93,7 @@ function roundcube_browser() } -// static functions for event handling +// static functions for DOM event handling var rcube_event = { /** @@ -159,8 +159,8 @@ get_mouse_pos: function(e) } 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 @@ cancel: function(evt) }; -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 @@ var rcube_layer_objects = new Array(); * @constructor */ function rcube_layer(id, attributes) - { +{ this.name = id; // create a new layer in the current document @@ -310,10 +389,6 @@ function rcube_layer(id, attributes) 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 @@ function rcube_layer(id, attributes) 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) { @@ -347,15 +412,6 @@ function rcube_layer(id, attributes) } - // 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; - } - - // show or hide the layer this.show = function(a) { @@ -383,36 +439,7 @@ function rcube_layer(id, attributes) 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 @@ function urlencode(str) // 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 @@ function rcube_find_object(id, d) 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); } - - 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}; + 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; +} + // 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 @@ function rcube_console() if (box) { if (msg.charAt(msg.length-1)=='\n') - msg += '--------------------------------------\n'; + msg += '--------------------------------------\n'; else msg += '\n--------------------------------------\n'; @@ -633,7 +606,8 @@ function rcube_console() } var bw = new roundcube_browser(); -var console = new rcube_console(); +if (!window.console) + console = new rcube_console(); // Add escape() method to RegExp object diff --git a/program/js/editor.js b/program/js/editor.js index 6b847ba00..7f937b2b8 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -52,8 +52,8 @@ function rcmail_editor_init(skin_path, editor_lang, spellcheck, mode) spellchecker_languages : (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'), gecko_spellcheck : true, relative_urls : false, - remove_script_host : false , - rc_client: rcube_webmail_client, + remove_script_host : false, + rc_client: rcmail, oninit : 'rcmail_editor_callback' }); } diff --git a/program/js/list.js b/program/js/list.js index 522af59ab..dabcecb92 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -51,7 +51,6 @@ function rcube_list_widget(list, p) 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 @@ remove_row: function(uid, sel_next) 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 @@ focus: function(e) 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 @@ blur: function() 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 @@ drag_row: function(e, id) 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 @@ click_row: function(e, id) // 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 @@ select_row: function(id, mod_key, with_mouse) // 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 @@ select_row: function(id, mod_key, with_mouse) 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 @@ select_all: function(filter) // trigger event if selection changed if (this.selection.join(',') != select_before) - this.trigger_event('select'); + this.triggerEvent('select'); this.focus(); @@ -543,27 +540,24 @@ clear_selection: function(id) 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 @@ highlight_row: function(id, multiple) { 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 @@ highlight_row: function(id, multiple) 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 @@ highlight_row: function(id, multiple) 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 @@ key_press: function(e) 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 @@ drag_mouse_move: function(e) 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 @@ drag_mouse_move: function(e) 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 @@ drag_mouse_move: function(e) } } - 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 @@ drag_mouse_up: function(e) { 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 @@ drag_mouse_up: function(e) } return rcube_event.cancel(e); -}, - - - -/** - * set/unset a specific class name - */ -set_classname: function(obj, classname, set) -{ - var reg = new RegExp('\s*'+classname, 'i'); - if (!set && obj.className.match(reg)) - obj.className = obj.className.replace(reg, ''); - else if (set && !obj.className.match(reg)) - obj.className += ' '+classname; -}, - - -/** - * Setter for object event handlers - * - * @param {String} Event name - * @param {Function} Handler function - * @return Listener ID (used to remove this handler later on) - */ -addEventListener: function(evt, handler) -{ - if (this.events[evt]) { - var handle = this.events[evt].length; - this.events[evt][handle] = handler; - return handle; - } - else - return false; -}, - - -/** - * Removes a specific event listener - * - * @param {String} Event name - * @param {Int} Listener ID to remove - */ -removeEventListener: function(evt, handle) -{ - if (this.events[evt] && this.events[evt][handle]) - this.events[evt][handle] = null; -}, - - -/** - * This will execute all registered event handlers - * @private - */ -trigger_event: function(evt, p) -{ - if (this.events[evt] && this.events[evt].length) { - for (var i=0; i<this.events[evt].length; i++) - if (typeof(this.events[evt][i]) == 'function') - this.events[evt][i](this, p); - } } - }; +rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; +rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; +rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; diff --git a/program/lib/imap.inc b/program/lib/imap.inc index 995d82fb6..967b3f160 100644 --- a/program/lib/imap.inc +++ b/program/lib/imap.inc @@ -182,6 +182,7 @@ class iilBasicHeader var $forwarded = false; var $junk = false; var $flagged = false; + var $others = array(); } /** @@ -1661,7 +1662,7 @@ function iil_IndexThreads(&$tree) { 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 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo } } } + + if ($add) + $add = ' '.strtoupper(trim($add)); /* FETCH uid, size, flags and headers */ $key = 'FH12'; @@ -1711,7 +1715,7 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo $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 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo 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 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo 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 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo return $result; } -function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false) { +function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false, $add='') { - $a = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr); + $a = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr, $add); if (is_array($a)) { return array_shift($a); } diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index 33dfad646..e3d7606cb 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -42,18 +42,20 @@ $OUTPUT->set_env('readonly', $CONTACTS->readonly, false); $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 @@ function rcmail_directory_list($attrib) $current = get_input_value('_source', RCUBE_INPUT_GPC); $line_templ = html::tag('li', array('id' => 'rcmli%s', 'class' => '%s'), html::a(array('href' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); - - if (strtolower($CONFIG['address_book_type']) != 'ldap') { - $out .= sprintf($line_templ, $local_id, (!$current ? 'selected' : ''), - Q(rcmail_url(null, array('_source' => $local_id))), $local_id, rcube_label('personaladrbook')); - } // end if - else { + + if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') { + $current = '0'; + } + else if (!$current) { // DB address book not used, see if a source is set, if not use the // first LDAP directory. - if (!$current) { - $current = key((array)$CONFIG['ldap_public']); - } // end if - } // end else - - foreach ((array)$CONFIG['ldap_public'] as $id => $prop) { + $current = key((array)$RCMAIL->config->get('ldap_public', array())); + } + + foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) { + $id = $source['id'] ? $source['id'] : $j; $js_id = JQ($id); $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id); $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''), - Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($prop['name']) ? Q($prop['name']) : Q($id))); + Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); } - + $OUTPUT->add_gui_object('folderlist', $attrib['id']); return html::tag('ul', $attrib, $out, html::$common_attrib); diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index f6e29f9d7..6d58edc8e 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -28,46 +28,45 @@ if (!$_SESSION['compose']) { // 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 @@ $OUTPUT->reset(); 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 @@ if (is_array($_FILES['_attachments']['tmp_name'])) { $content = html::a(array( 'href' => "#delete", - 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%d', this)", JS_OBJECT_NAME, $id), + 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id), 'title' => rcube_label('delete'), ), $button); - - $content .= Q($_FILES['_attachments']['name'][$i]); + + $content .= Q($attachment['name']); $OUTPUT->command('add2attachment_list', "rcmfile$id", $content); } diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 49c4c3011..c93fa9be2 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -594,8 +594,6 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml) 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 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml) || (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 @@ function rcmail_write_inline_attachments(&$message) 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_write_inline_attachments(&$message) 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 @@ function rcmail_compose_attachment_list($attrib) html::a(array( 'href' => "#delete", 'title' => rcube_label('delete'), - 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%d', this)", JS_OBJECT_NAME, $id)), + 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id)), $button) . Q($a_prop['name'])); } } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 8931cfa4e..28ae70ca3 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -730,71 +730,86 @@ function rcmail_wash_html($html, $p = array(), $cid_replaces) */ 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; + $part->ctype_secondary = $data['type'] = 'plain'; + } + + // free some memory (hopefully) + unset($data['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; + // 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); + 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 } - 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); - // insert the links for urls and mailtos - $body = $replacements->resolve(join("\n", $a_lines)); - - return html::tag('pre', array(), $body); + return $data['type'] == 'html' ? $data['body'] : html::tag('pre', array(), $data['body']); } @@ -842,7 +857,7 @@ function rcmail_washtml_callback($tagname, $attrib, $content) */ 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 @@ function rcmail_message_headers($attrib, $headers=NULL) else if (!is_array($attrib) && is_array($sa_attrib)) $attrib = $sa_attrib; - if (!isset($MESSAGE)) return FALSE; @@ -859,58 +873,55 @@ function rcmail_message_headers($attrib, $headers=NULL) 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'); + $header_value = rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']); else if ($hkey == 'subject' && empty($headers[$hkey])) - $header_value = Q(rcube_label('nosubject')); + $header_value = 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)).": </td>\n"; - $out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>"; - $header_count++; - } + $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 @@ function rcmail_compose_cleanup() if (!isset($_SESSION['compose'])) return; - // remove attachment files from temp dir - if (is_array($_SESSION['compose']['attachments'])) - foreach ($_SESSION['compose']['attachments'] as $attachment) - @unlink($attachment['path']); + rcmail::get_instance()->plugins->exec_hook('cleanup_attachments',array()); unset($_SESSION['compose']); } diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 9607619e9..34e2c0904 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -297,86 +297,92 @@ $MAIL_MIME = new rcube_mail_mime($RCMAIL->config->header_delimiter()); // 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 @@ $MAIL_MIME->setParam(array( 'text_charset' => $message_charset, )); +$data = $RCMAIL->plugins->exec_hook('outgoing_message_headers', array('headers' => $headers)); +$headers = $data['headers']; + // encoding subject header with mb_encode provides better results with asian characters if (function_exists("mb_encode_mimeheader")) { diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index fd31fa91c..9beb42521 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -135,9 +135,11 @@ if ($_GET['_uid']) { } // mark message as read - if (!$MESSAGE->headers->seen) + if (!$MESSAGE->headers->seen) { $IMAP->set_flag($MESSAGE->uid, 'SEEN'); + $RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid, 'mailbox' => $IMAP->mailbox, 'message' => $MESSAGE)); } +} diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 3f0357717..6eda4dba3 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -159,6 +159,8 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) } } + $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 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) $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; @@ -254,6 +258,8 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) $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)); break; @@ -295,6 +301,8 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) $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; @@ -329,6 +337,8 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) $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)); } break; @@ -381,6 +391,8 @@ function rcmail_user_prefs_block($part, $no_override, $attrib) $table->add(null, $input_expunge->show($config['logout_expunge']?1:0)); } + $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table)); + if ($table->size()) $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('serversettings'))) . $table->show($attrib)); break; diff --git a/program/steps/settings/manage_folders.inc b/program/steps/settings/manage_folders.inc index 9affded98..79d313c37 100644 --- a/program/steps/settings/manage_folders.inc +++ b/program/steps/settings/manage_folders.inc @@ -256,6 +256,7 @@ function rcube_subscription_form($attrib) $a_js_folders['rcmrow'.$idx] = array($folder_utf8, $display_folder, $protected || $folder['virtual']); } + rcmail::get_instance()->plugins->exec_hook('manage_folders', array('table'=>$table)); $OUTPUT->add_gui_object('subscriptionlist', $attrib['id']); $OUTPUT->set_env('subscriptionrows', $a_js_folders); diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index 09cf63d6f..c5afd5b0c 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -48,6 +48,9 @@ $a_user_prefs = array( 'trash_mbox' => get_input_value('_trash_mbox', RCUBE_INPUT_POST), ); +$data = rcmail::get_instance()->plugins->exec_hook('save_preferences', array('prefs' => $a_user_prefs)); +$a_user_prefs = $data['prefs']; + // don't override these parameters foreach ((array)$CONFIG['dont_override'] as $p) $a_user_prefs[$p] = $CONFIG[$p]; diff --git a/skins/default/common.css b/skins/default/common.css index 34ea1d2b8..631321c72 100644 --- a/skins/default/common.css +++ b/skins/default/common.css @@ -246,6 +246,13 @@ a.button-logout border: 1px solid #CCCCCC; } +#pagecontent +{ + position: absolute; + top: 95px; + left: 20px; +} + .splitter { user-select: none; diff --git a/skins/default/functions.js b/skins/default/functions.js index 9e71f6f9a..fd6e612ee 100644 --- a/skins/default/functions.js +++ b/skins/default/functions.js @@ -8,24 +8,16 @@ function rcube_init_settings_tabs() { + var tab = '#settingstabdefault'; if (window.rcmail && rcmail.env.action) - { - var action = rcmail.env.action=='preferences' ? 'default' : (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action); - var tab = document.getElementById('settingstab'+action); - } - else - var tab = document.getElementById('settingstabdefault'); - - if (tab) - tab.className = 'tablink-selected'; + tab = '#settingstab' + (rcmail.env.action=='preferences' ? 'default' : (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action.replace(/\./g, ''))); + + $(tab).addClass('tablink-selected'); } function rcube_show_advanced(visible) { - var rows = document.getElementsByTagName('TR'); - for(var i=0; i<rows.length; i++) - if(rows[i].className && rows[i].className.match(/advanced/)) - rows[i].style.display = visible ? (bw.ie ? 'block' : 'table-row') : 'none'; + $('tr.advanced').css('display', (visible ? (bw.ie ? 'block' : 'table-row') : 'none')); } /** @@ -128,7 +120,7 @@ function rcmail_init_compose_form() function rcube_mail_ui() { - this.markmenu = new rcube_layer('markmessagemenu'); + this.markmenu = $('#markmessagemenu'); } rcube_mail_ui.prototype = { @@ -136,24 +128,24 @@ rcube_mail_ui.prototype = { show_markmenu: function(show) { if (typeof show == 'undefined') - show = this.markmenu.visible ? false : true; + show = this.markmenu.is(':visible') ? false : true; var ref = rcube_find_object('markreadbutton'); if (show && ref) - this.markmenu.move(ref.offsetLeft, ref.offsetTop + ref.offsetHeight); + this.markmenu.css({ left:ref.offsetLeft, top:(ref.offsetTop + ref.offsetHeight) }); - this.markmenu.show(show); + this.markmenu[show?'show':'hide'](); }, body_mouseup: function(evt, p) { - if (this.markmenu && this.markmenu.visible && rcube_event.get_target(evt) != rcube_find_object('markreadbutton')) + if (this.markmenu && this.markmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('markreadbutton')) this.show_markmenu(false); }, body_keypress: function(evt, p) { - if (rcube_event.get_keycode(evt) == 27 && this.markmenu && this.markmenu.visible) + if (rcube_event.get_keycode(evt) == 27 && this.markmenu && this.markmenu.is(':visible')) this.show_markmenu(false); } diff --git a/skins/default/includes/settingstabs.html b/skins/default/includes/settingstabs.html index 5121ba19b..ce6d23407 100644 --- a/skins/default/includes/settingstabs.html +++ b/skins/default/includes/settingstabs.html @@ -2,4 +2,6 @@ <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span> <span id="settingstabfolders" class="tablink"><roundcube:button command="folders" type="link" label="folders" title="managefolders" class="tablink" /></span> <span id="settingstabidentities" class="tablink"><roundcube:button command="identities" type="link" label="identities" title="manageidentities" class="tablink" /></span> +<roundcube:container name="tabs" id="tabsbar" /> +<script type="text/javascript"> if (window.rcmail) rcmail.add_onload(rcube_init_settings_tabs); </script> </div> diff --git a/skins/default/includes/taskbar.html b/skins/default/includes/taskbar.html index ef1aa8268..c2841a606 100644 --- a/skins/default/includes/taskbar.html +++ b/skins/default/includes/taskbar.html @@ -1,4 +1,5 @@ <div id="taskbar"> +<roundcube:container name="taskbar" id="taskbar" /> <roundcube:button command="mail" label="mail" class="button-mail" /> <roundcube:button command="addressbook" label="addressbook" class="button-addressbook" /> <roundcube:button command="settings" label="settings" class="button-settings" /> diff --git a/skins/default/mail.css b/skins/default/mail.css index 5a4e57bfe..ab4579eb9 100644 --- a/skins/default/mail.css +++ b/skins/default/mail.css @@ -49,7 +49,7 @@ top: 32px; left: 90px; width: auto; - visibility: hidden; + display: none; background-color: #F9F9F9; border: 1px solid #CCC; padding: 1px; diff --git a/skins/default/splitter.js b/skins/default/splitter.js index 3ed0eb62a..fae3ca5cb 100644 --- a/skins/default/splitter.js +++ b/skins/default/splitter.js @@ -22,18 +22,18 @@ function rcube_splitter(attrib) 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 @@ function rcube_splitter(attrib) 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 @@ function rcube_splitter(attrib) */ 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 @@ function rcube_splitter(attrib) // 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 @@ function rcube_splitter(attrib) 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 @@ function rcube_splitter(attrib) } 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 @@ function rcube_splitter(attrib) if (this.iframe_events[n]) { if (iframedoc.removeEventListener) iframedoc.removeEventListener('mousemove', this.iframe_events[n], false); - else if (iframedoc.detachEvent) + else if (iframedoc.detachEvent) iframedoc.detachEvent('onmousemove', this.iframe_events[n]); else iframedoc['onmousemove'] = null; diff --git a/skins/default/templates/addressbook.html b/skins/default/templates/addressbook.html index ce295567b..431c0589e 100644 --- a/skins/default/templates/addressbook.html +++ b/skins/default/templates/addressbook.html @@ -7,7 +7,7 @@ <script type="text/javascript" src="/splitter.js"></script> <style type="text/css"> -<roundcube:if condition="config:ldap_public == false" /> +<roundcube:if condition="count(env:address_sources) <= 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) > 1" /> <div id="directorylist"> <div id="groups-title"><roundcube:label name="groups" /></div> <roundcube:object name="directorylist" id="directories-list" /> diff --git a/skins/default/templates/identities.html b/skins/default/templates/identities.html index 9799bc576..30d33a38b 100644 --- a/skins/default/templates/identities.html +++ b/skins/default/templates/identities.html @@ -6,7 +6,7 @@ <link rel="stylesheet" type="text/css" href="/settings.css" /> <script type="text/javascript" src="/functions.js"></script> </head> -<body onload="rcube_init_settings_tabs()"> +<body> <roundcube:include file="/includes/taskbar.html" /> <roundcube:include file="/includes/header.html" /> diff --git a/skins/default/templates/mail.html b/skins/default/templates/mail.html index 96be0f65e..4e1d7ce3a 100644 --- a/skins/default/templates/mail.html +++ b/skins/default/templates/mail.html @@ -119,6 +119,7 @@ <roundcube:button command="forward" imageSel="/images/buttons/forward_sel.png" imageAct="/images/buttons/forward_act.png" imagePas="/images/buttons/forward_pas.png" width="32" height="32" title="forwardmessage" /> <roundcube:button command="delete" imageSel="/images/buttons/delete_sel.png" imageAct="/images/buttons/delete_act.png" imagePas="/images/buttons/delete_pas.png" width="32" height="32" title="deletemessage" /> <roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" /> +<roundcube:container name="toolbar" id="messagetoolbar" /> <div id="markmessagemenu"> <ul class="toolbarmenu"> diff --git a/skins/default/templates/managefolders.html b/skins/default/templates/managefolders.html index 5da5c22f1..925bc2c81 100644 --- a/skins/default/templates/managefolders.html +++ b/skins/default/templates/managefolders.html @@ -6,7 +6,7 @@ <link rel="stylesheet" type="text/css" href="/settings.css" /> <script type="text/javascript" src="/functions.js"></script> </head> -<body onload="rcube_init_settings_tabs()"> +<body> <roundcube:include file="/includes/taskbar.html" /> <roundcube:include file="/includes/header.html" /> diff --git a/skins/default/templates/message.html b/skins/default/templates/message.html index b8d66c1be..7d42ef73f 100644 --- a/skins/default/templates/message.html +++ b/skins/default/templates/message.html @@ -36,6 +36,7 @@ <roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" /> <roundcube:button command="viewsource" imageSel="/images/buttons/source_sel.png" imageAct="/images/buttons/source_act.png" imagePas="/images/buttons/source_pas.png" width="32" height="32" title="viewsource" /> <roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('moveto', this.options[this.selectedIndex].value)" class="mboxlist" /> +<roundcube:container name="toolbar" id="messagetoolbar" /> </div> <div id="mainscreen"> diff --git a/skins/default/templates/plugin.html b/skins/default/templates/plugin.html new file mode 100644 index 000000000..9725fe4d8 --- /dev/null +++ b/skins/default/templates/plugin.html @@ -0,0 +1,24 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title><roundcube:object name="pagetitle" /></title> +<roundcube:include file="/includes/links.html" /> +<link rel="stylesheet" type="text/css" href="/<roundcube:var name='env:task'/>.css" /> +<script type="text/javascript" src="/functions.js"></script> +</head> +<body> + +<roundcube:include file="/includes/taskbar.html" /> +<roundcube:include file="/includes/header.html" /> +<roundcube:if condition="env:task == 'settings'" /> + <roundcube:include file="/includes/settingstabs.html" /> +<roundcube:endif /> + +<div id="pagecontent"> +<roundcube:object name="plugin.body" /> +</div> + +<roundcube:object name="plugin.footer" /> + +</body> +</html> diff --git a/skins/default/templates/settings.html b/skins/default/templates/settings.html index a3f5298cd..0abe7fa41 100644 --- a/skins/default/templates/settings.html +++ b/skins/default/templates/settings.html @@ -6,7 +6,7 @@ <link rel="stylesheet" type="text/css" href="/settings.css" /> <script type="text/javascript" src="/functions.js"></script> </head> -<body onload="rcube_init_settings_tabs()"> +<body> <roundcube:include file="/includes/taskbar.html" /> <roundcube:include file="/includes/header.html" /> @@ -17,7 +17,7 @@ <div id="userprefs-box"> <div id="userprefs-title"><roundcube:label name="userpreferences" /></div> -<div style="padding:15px 0 15px 15px"> +<div id="userprefscontainer" style="padding:15px 0 15px 15px"> <div class="userprefs-block"> <roundcube:object name="userprefs" form="form" parts="general,mailbox,mailview" /> </div> @@ -25,6 +25,8 @@ <roundcube:object name="userprefs" form="form" parts="compose,folders,server" /> </div> <div style="clear:left"></div> + +<roundcube:container name="userprefs" id="userprefscontainer" /> </div> </div> |