From cc97ea0559af1a92a54dbcdf738ee4d95e67d3ff Mon Sep 17 00:00:00 2001 From: thomascube Date: Sun, 19 Apr 2009 17:44:29 +0000 Subject: Merged branch devel-api (from r2208 to r2387) back into trunk (omitting some sample plugins) --- config/main.inc.php.dist | 8 +- index.php | 48 +- .../additional_message_headers.php | 42 + plugins/autologon/autologon.php | 44 + .../database_attachments/database_attachments.php | 152 ++++ plugins/debug_logger/debug_logger.php | 146 +++ plugins/debug_logger/runlog/runlog.php | 227 +++++ plugins/emoticons/emoticons.php | 39 + .../example_addressbook/example_addressbook.php | 42 + .../example_addressbook_backend.php | 72 ++ .../filesystem_attachments.php | 144 +++ .../http_authentication/http_authentication.php | 41 + plugins/markasjunk/junk_act.png | Bin 0 -> 1995 bytes plugins/markasjunk/junk_pas.png | Bin 0 -> 1988 bytes plugins/markasjunk/localization/en_US.inc | 7 + plugins/markasjunk/markasjunk.js | 28 + plugins/markasjunk/markasjunk.php | 47 + plugins/new_user_identity/new_user_identity.php | 49 + plugins/password/localization/en_US.inc | 15 + plugins/password/localization/pl_PL.inc | 15 + plugins/password/password.js | 44 + plugins/password/password.php | 160 ++++ .../show_additional_headers.php | 49 + .../subscriptions_option/localization/en_US.inc | 6 + .../subscriptions_option/subscriptions_option.php | 84 ++ plugins/userinfo/localization/de_CH.inc | 9 + plugins/userinfo/localization/en_US.inc | 9 + plugins/userinfo/userinfo.js | 16 + plugins/userinfo/userinfo.php | 53 ++ plugins/vcard_attachments/vcard_attachments.php | 115 +++ plugins/vcard_attachments/vcardattach.js | 10 + program/include/html.php | 30 +- program/include/iniset.php | 2 +- program/include/main.inc | 25 +- program/include/rcmail.php | 44 +- program/include/rcube_addressbook.php | 169 ++++ program/include/rcube_config.php | 1 + program/include/rcube_contacts.php | 35 +- program/include/rcube_html_page.php | 11 +- program/include/rcube_imap.php | 31 +- program/include/rcube_json_output.php | 26 +- program/include/rcube_ldap.php | 2 +- program/include/rcube_message.php | 3 + program/include/rcube_plugin.php | 196 ++++ program/include/rcube_plugin_api.php | 312 +++++++ program/include/rcube_template.php | 112 ++- program/include/rcube_user.php | 26 +- program/include/rcube_vcard.php | 4 +- program/js/app.js | 997 ++++++++------------- program/js/common.js | 230 +++-- program/js/editor.js | 4 +- program/js/list.js | 172 ++-- program/lib/imap.inc | 18 +- program/steps/addressbook/func.inc | 36 +- program/steps/mail/attachments.inc | 74 +- program/steps/mail/compose.inc | 50 +- program/steps/mail/func.inc | 176 ++-- program/steps/mail/sendmail.inc | 87 +- program/steps/mail/show.inc | 4 +- program/steps/settings/func.inc | 12 + program/steps/settings/manage_folders.inc | 1 + program/steps/settings/save_prefs.inc | 3 + skins/default/common.css | 7 + skins/default/functions.js | 30 +- skins/default/includes/settingstabs.html | 2 + skins/default/includes/taskbar.html | 1 + skins/default/mail.css | 2 +- skins/default/splitter.js | 40 +- skins/default/templates/addressbook.html | 4 +- skins/default/templates/identities.html | 2 +- skins/default/templates/mail.html | 1 + skins/default/templates/managefolders.html | 2 +- skins/default/templates/message.html | 1 + skins/default/templates/plugin.html | 24 + skins/default/templates/settings.html | 6 +- 75 files changed, 3466 insertions(+), 1270 deletions(-) create mode 100644 plugins/additional_message_headers/additional_message_headers.php create mode 100644 plugins/autologon/autologon.php create mode 100644 plugins/database_attachments/database_attachments.php create mode 100644 plugins/debug_logger/debug_logger.php create mode 100644 plugins/debug_logger/runlog/runlog.php create mode 100644 plugins/emoticons/emoticons.php create mode 100644 plugins/example_addressbook/example_addressbook.php create mode 100644 plugins/example_addressbook/example_addressbook_backend.php create mode 100644 plugins/filesystem_attachments/filesystem_attachments.php create mode 100644 plugins/http_authentication/http_authentication.php create mode 100644 plugins/markasjunk/junk_act.png create mode 100644 plugins/markasjunk/junk_pas.png create mode 100644 plugins/markasjunk/localization/en_US.inc create mode 100644 plugins/markasjunk/markasjunk.js create mode 100644 plugins/markasjunk/markasjunk.php create mode 100644 plugins/new_user_identity/new_user_identity.php create mode 100644 plugins/password/localization/en_US.inc create mode 100644 plugins/password/localization/pl_PL.inc create mode 100644 plugins/password/password.js create mode 100644 plugins/password/password.php create mode 100644 plugins/show_additional_headers/show_additional_headers.php create mode 100644 plugins/subscriptions_option/localization/en_US.inc create mode 100644 plugins/subscriptions_option/subscriptions_option.php create mode 100644 plugins/userinfo/localization/de_CH.inc create mode 100644 plugins/userinfo/localization/en_US.inc create mode 100644 plugins/userinfo/userinfo.js create mode 100644 plugins/userinfo/userinfo.php create mode 100644 plugins/vcard_attachments/vcard_attachments.php create mode 100644 plugins/vcard_attachments/vcardattach.js create mode 100644 program/include/rcube_addressbook.php create mode 100644 program/include/rcube_plugin.php create mode 100644 program/include/rcube_plugin_api.php create mode 100644 skins/default/templates/plugin.html 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'; diff --git a/index.php b/index.php index 172d57c88..7c2d23032 100644 --- a/index.php +++ b/index.php @@ -2,7 +2,7 @@ /* +-------------------------------------------------------------------------+ | RoundCube Webmail IMAP Client | - | Version 0.2-20080829 | + | Version 0.3-20090419 | | | | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland | | | @@ -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 @@ +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 @@ +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 @@ + + * + */ +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 @@ +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 @@ + + */ +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 = '
' . print_r( $msg, TRUE ) . '
'; + } + $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 @@ +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 @@ +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 @@ +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 @@ + + * @author Thomas Bruederli + * + */ +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 @@ +add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function startup($args) + { + // change action to login + if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) + && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) + $args['action'] = 'login'; + + return $args; + } + + function authenticate($args) + { + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $args['user'] = $_SERVER['PHP_AUTH_USER']; + $args['pass'] = $_SERVER['PHP_AUTH_PW']; + } + + return $args; + } + +} + diff --git a/plugins/markasjunk/junk_act.png b/plugins/markasjunk/junk_act.png new file mode 100644 index 000000000..b5a84f604 Binary files /dev/null and b/plugins/markasjunk/junk_act.png differ diff --git a/plugins/markasjunk/junk_pas.png b/plugins/markasjunk/junk_pas.png new file mode 100644 index 000000000..b88a561a4 Binary files /dev/null and b/plugins/markasjunk/junk_pas.png differ 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 @@ + \ 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 @@ +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 @@ +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 @@ + \ 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 @@ + 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) { + // + var tab = $('').attr('id', 'settingstabpluginpassword').addClass('tablink'); + + var button = $('').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 @@ + 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 tag + $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); + + // return the complete edit form as table + $out = '\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("\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("\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("\n", + $field_id, + rep_specialchars_output($this->gettext('confpasswd')), + $input_confpasswd->show($rcmail->config->get('confpasswd'))); + + $out .= "\n
%s
%s
%s
"; + + $out .= '
'; + + $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 @@ +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 @@ + 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 @@ +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 @@ + \ 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 @@ + \ 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) { + // + var tab = $('').attr('id', 'settingstabpluginuserinfo').addClass('tablink'); + + var button = $('
').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 @@ +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 @@ +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 @@ + | + +-----------------------------------------------------------------------+ + + $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 = ''; @@ -59,30 +59,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 * @@ -117,13 +93,6 @@ class rcube_contacts } - /** - * Close connection to source - * Called on script shutdown - */ - function close(){} - - /** * List the current set of contact records * @@ -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 = "\n"; - protected $script_tag = "\n"; + protected $script_tag_file = "\n"; + protected $script_tag = ""; protected $default_template = "\n\n\n"; 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 @@ + | + +-----------------------------------------------------------------------+ + + $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 + * 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 @@ + | + +-----------------------------------------------------------------------+ + + $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('/]+)>/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('/]+)>/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 | | Charles McNulty | +-----------------------------------------------------------------------+ - | 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= 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 = '

--

' + 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); + $('
  • ').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 = $('
      '); + this.ksearch_pane = $('
      ').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'+cont+'
      '; - 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 = $('').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 ? '' : ''; - row.appendChild(col); + // add icon col + $('').addClass('icon').html(icon ? '' : '').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 = $('').addClass(String(c).toLowerCase()); - if (c=='flag') - { + if (c=='flag') { if (flags.flagged && this.env.flaggedicon) - col.innerHTML = ''; + col.html(''); else if(!flags.flagged && this.env.unflaggedicon) - col.innerHTML = ''; + col.html(''); } else if (c=='attachment') - col.innerHTML = attachment && this.env.attachmenticon ? '' : ' '; + col.html(attachment && this.env.attachmenticon ? '' : ' '); 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 = $('').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 = $('').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 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= 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').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; ipriority = 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('
      ', $q - $quote_level); - else if ($q < $quote_level) - $quotation = str_repeat("
      ", $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('
      ', $q - $quote_level); + else if ($q < $quote_level) + $quotation = str_repeat("
      ", $quote_level - $q); + } + else if ($quote_level > 0) + $quotation = str_repeat("", $quote_level); + + $quote_level = $q; + $a_lines[$n] = $quotation . Q($line, 'replace', false); // htmlquote plaintext } - else if ($quote_level > 0) - $quotation = str_repeat("", $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 tag - $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); - $out = '\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\n"; - $out .= '\n"; - $out .= '\n"; - $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".''; - $out .= "\n".''; - + $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
      '.Q(rcube_label($hkey)).": '.$header_value."
      \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
      ".$footer.'
      ' : '')); +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
      ".$footer.'
      ' : '')); // 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 + + 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 @@
      + 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 @@