diff --git a/CHANGELOG b/CHANGELOG index bffffd612..a7e71daf6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ CHANGELOG Roundcube Webmail =========================== +- Fix imap_cache setting to values other than 'db' (#1488060) +- Fix handling of attachments inside message/rfc822 parts (#1488026) +- Make list of mimetypes that open in preview window configurable (#1487625) +- Added plugin hook 'message_part_get' for attachment downloads - Fixed selecting identity on reply/forward (#1487981) - Fix image type check for contact photo uploads diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 85afa8c7f..94bb79fb1 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -326,6 +326,11 @@ $rcmail_config['dont_override'] = array(); // 3 - one identity with possibility to edit all params but not email address $rcmail_config['identities_level'] = 0; +// Mimetypes supported by the browser. +// attachments of these types will open in a preview window +// either a comma-separated list or an array: 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/pdf' +$rcmail_config['client_mimetypes'] = null; # null == default + // mime magic database $rcmail_config['mime_magic'] = '/usr/share/misc/magic'; diff --git a/plugins/acl/acl.js b/plugins/acl/acl.js index d78607690..4b1431ac3 100644 --- a/plugins/acl/acl.js +++ b/plugins/acl/acl.js @@ -1,7 +1,7 @@ /** * ACL plugin script * - * @version 0.6 + * @version 0.6.1 * @author Aleksander Machniak */ @@ -11,7 +11,7 @@ if (window.rcmail) { rcmail.acl_list_init(); // enable autocomplete on user input if (rcmail.env.acl_users_source) { - rcmail.init_address_input_events($('#acluser'), {action:'plugin.acl-autocomplete'}); + rcmail.init_address_input_events($('#acluser'), {action:'settings/plugin.acl-autocomplete'}); // fix inserted value rcmail.addEventListener('autocomplete_insert', function(e) { if (e.field.id != 'acluser') @@ -52,7 +52,7 @@ rcube_webmail.prototype.acl_delete = function() var users = this.acl_get_usernames(); if (users && users.length && confirm(this.get_label('acl.deleteconfirm'))) { - this.http_request('plugin.acl', '_act=delete&_user='+urlencode(users.join(',')) + this.http_request('settings/plugin.acl', '_act=delete&_user='+urlencode(users.join(',')) + '&_mbox='+urlencode(this.env.mailbox), this.set_busy(true, 'acl.deleting')); } @@ -82,7 +82,7 @@ rcube_webmail.prototype.acl_save = function() return; } - this.http_request('plugin.acl', '_act=save' + this.http_request('settings/plugin.acl', '_act=save' + '&_user='+urlencode(user) + '&_acl=' +rights + '&_mbox='+urlencode(this.env.mailbox) @@ -120,7 +120,7 @@ rcube_webmail.prototype.acl_mode_switch = function(elem) { this.env.acl_advanced = !this.env.acl_advanced; this.enable_command('acl-delete', 'acl-edit', false); - this.http_request('plugin.acl', '_act=list' + this.http_request('settings/plugin.acl', '_act=list' + '&_mode='+(this.env.acl_advanced ? 'advanced' : 'simple') + '&_mbox='+urlencode(this.env.mailbox), this.set_busy(true, 'loading')); diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php index 1b021d00a..976b36260 100644 --- a/plugins/acl/acl.php +++ b/plugins/acl/acl.php @@ -3,7 +3,7 @@ /** * Folders Access Control Lists Management (RFC4314, RFC2086) * - * @version 0.6 + * @version 0.6.1 * @author Aleksander Machniak * * @@ -25,7 +25,7 @@ class acl extends rcube_plugin { - public $task = 'settings|addressbook'; + public $task = 'settings|addressbook|calendar'; private $rc; private $supported = null; @@ -44,6 +44,7 @@ class acl extends rcube_plugin $this->add_hook('folder_form', array($this, 'folder_form')); // kolab_addressbook plugin $this->add_hook('addressbook_form', array($this, 'folder_form')); + $this->add_hook('calendar_form_kolab', array($this, 'folder_form')); // Plugin actions $this->register_action('plugin.acl', array($this, 'acl_actions')); $this->register_action('plugin.acl-autocomplete', array($this, 'acl_autocomplete')); @@ -526,7 +527,7 @@ class acl extends rcube_plugin $list = array(); $attrib = array( 'name' => 'rcmyrights', - 'style' => 'padding: 0 15px;', + 'style' => 'margin:0; padding:0 15px;', ); foreach ($supported as $right) { diff --git a/plugins/acl/skins/default/acl.css b/plugins/acl/skins/default/acl.css index cd8f4b72e..e46a1d00b 100644 --- a/plugins/acl/skins/default/acl.css +++ b/plugins/acl/skins/default/acl.css @@ -81,7 +81,7 @@ #aclform { - top: 100px; + top: 80px; width: 480px; padding: 10px; } diff --git a/plugins/new_user_dialog/new_user_dialog.php b/plugins/new_user_dialog/new_user_dialog.php index 0d6837335..9d7cbcba5 100644 --- a/plugins/new_user_dialog/new_user_dialog.php +++ b/plugins/new_user_dialog/new_user_dialog.php @@ -66,6 +66,20 @@ class new_user_dialog extends rcube_plugin 'disabled' => ($identities_level == 1 || $identities_level == 3) ))); + $table->add('title', $this->gettext('organization')); + $table->add(null, html::tag('input', array( + 'type' => 'text', + 'name' => '_organization', + 'value' => $identity['organization'] + ))); + + $table->add('title', $this->gettext('signature')); + $table->add(null, html::tag('textarea', array( + 'name' => '_signature', + 'rows' => '3', + ),$identity['signature'] + )); + // add overlay input box to html page $rcmail->output->add_footer(html::div(array('id' => 'newuseroverlay'), html::tag('form', array( @@ -106,6 +120,8 @@ class new_user_dialog extends rcube_plugin $save_data = array( 'name' => get_input_value('_name', RCUBE_INPUT_POST), 'email' => get_input_value('_email', RCUBE_INPUT_POST), + 'organization' => get_input_value('_organization', RCUBE_INPUT_POST), + 'signature' => get_input_value('_signature', RCUBE_INPUT_POST), ); // don't let the user alter the e-mail address if disabled by config @@ -125,4 +141,4 @@ class new_user_dialog extends rcube_plugin } -?> \ No newline at end of file +?> diff --git a/plugins/new_user_dialog/newuserdialog.css b/plugins/new_user_dialog/newuserdialog.css index c03e6fd42..1c3e652b6 100644 --- a/plugins/new_user_dialog/newuserdialog.css +++ b/plugins/new_user_dialog/newuserdialog.css @@ -48,7 +48,8 @@ white-space: nowrap; } -#newuseroverlay table td input +#newuseroverlay table td input, +#newuseroverlay table td textarea { width: 20em; } diff --git a/program/include/main.inc b/program/include/main.inc index 0401fe2c1..7e85e01fb 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -1021,7 +1021,7 @@ function rcube_strtotime($date) */ function format_date($date, $format=NULL) { - global $CONFIG; + global $RCMAIL, $CONFIG; $ts = NULL; @@ -1032,13 +1032,7 @@ function format_date($date, $format=NULL) return ''; // get user's timezone - if ($CONFIG['timezone'] === 'auto') - $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600; - else { - $tz = $CONFIG['timezone']; - if ($CONFIG['dst_active']) - $tz++; - } + $tz = $RCMAIL->config->get_timezone(); // convert time to user's timezone $timestamp = $ts - date('Z', $ts) + ($tz * 3600); @@ -1659,12 +1653,13 @@ function rcmail_replace_emoticons($html) * @param string $from Sender address string * @param array $mailto Array of recipient address strings * @param array $smtp_error SMTP error array (reference) - * @param string $body_file Location of file with saved message body (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled * @param array $smtp_opts SMTP options (e.g. DSN request) * * @return boolean Send status. */ -function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file, $smtp_opts=null) +function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null) { global $CONFIG, $RCMAIL; @@ -1823,17 +1818,10 @@ function rcmail_gen_message_id() // Returns RFC2822 formatted current date in user's timezone function rcmail_user_date() { - global $CONFIG; + global $RCMAIL, $CONFIG; // get user's timezone - if ($CONFIG['timezone'] === 'auto') { - $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600; - } - else { - $tz = $CONFIG['timezone']; - if ($CONFIG['dst_active']) - $tz++; - } + $tz = $RCMAIL->config->get_timezone(); $date = time() + $tz * 60 * 60; $date = gmdate('r', $date); diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 1e13624d7..03b536b3e 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -413,7 +413,10 @@ class rcmail $id = '0'; // use existing instance - if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) { + if (isset($this->address_books[$id]) && is_object($this->address_books[$id]) + && is_a($this->address_books[$id], 'rcube_addressbook') + && (!$writeable || !$this->address_books[$id]->readonly) + ) { $contacts = $this->address_books[$id]; } else if ($id && $ldap_config[$id]) { @@ -1183,7 +1186,7 @@ class rcmail $this->smtp->disconnect(); foreach ($this->address_books as $book) { - if (is_a($book, 'rcube_addressbook')) + if (is_object($book) && is_a($book, 'rcube_addressbook')) $book->close(); } diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 9379e9e7b..31b7ed6b7 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -220,6 +220,19 @@ class rcube_config return $this->prop; } + /** + * Special getter for user's timezone + */ + public function get_timezone() + { + $tz = $this->get('timezone'); + if ($tz == 'auto') + $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z') / 3600; + else + $tz = intval($tz) + intval($this->get('dst_active')); + + return $tz; + } /** * Return requested DES crypto key. diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php index 200233c5d..21301e331 100644 --- a/program/include/rcube_html_page.php +++ b/program/include/rcube_html_page.php @@ -212,7 +212,7 @@ class rcube_html_page if (!empty($this->scripts['docready'])) { $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot'); } - + if (is_array($this->script_files['foot'])) { foreach ($this->script_files['foot'] as $file) { $page_footer .= sprintf($this->script_tag_file, $file); @@ -246,7 +246,7 @@ class rcube_html_page // add page hader if ($hpos) { - $output = substr($output,0,$hpos) . $page_header . substr($output,$hpos,strlen($output)); + $output = substr_replace($output, $page_header, $hpos, 0); } else { $output = $page_header . $output; @@ -254,7 +254,7 @@ class rcube_html_page // add page footer if (($fpos = strripos($output, '')) || ($fpos = strripos($output, ''))) { - $output = substr($output, 0, $fpos) . "$page_footer\n" . substr($output, $fpos); + $output = substr_replace($output, $page_footer."\n", $fpos, 0); } else { $output .= "\n".$page_footer; @@ -268,7 +268,7 @@ class rcube_html_page foreach ($this->css_files as $file) { $css .= sprintf($this->link_css_file, $file); } - $output = substr($output, 0, $pos) . $css . substr($output, $pos); + $output = substr_replace($output, $css, $pos, 0); } $this->base_path = $base_path; @@ -289,7 +289,7 @@ class rcube_html_page echo $hook['content']; } } - + /** * Callback function for preg_replace_callback in write() * diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 6a35af0a8..1b311127c 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -3074,6 +3074,9 @@ class rcube_imap if (isset($data['folders'])) { $a_folders = $data['folders']; } + else if (!$this->conn->connected()) { + return array(); + } else { // Server supports LIST-EXTENDED, we can use selection options $config = rcmail::get_instance()->config; @@ -3834,13 +3837,13 @@ class rcube_imap /** * Enable or disable indexes caching * - * @param boolean $type Cache type (@see rcmail::get_cache) + * @param string $type Cache type (@see rcmail::get_cache) * @access public */ function set_caching($type) { if ($type) { - $this->caching = true; + $this->caching = $type; } else { if ($this->cache) @@ -3857,7 +3860,7 @@ class rcube_imap { if ($this->caching && !$this->cache) { $rcmail = rcmail::get_instance(); - $this->cache = $rcmail->get_cache('IMAP', $type); + $this->cache = $rcmail->get_cache('IMAP', $this->caching); } return $this->cache; diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 4f9a2f230..4e2595550 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -286,7 +286,7 @@ class rcube_message if ($message_ctype_primary == 'text' && !$recursive) { $structure->type = 'content'; $this->parts[] = &$structure; - + // Parse simple (plain text) message body if ($message_ctype_secondary == 'plain') foreach ((array)$this->uu_decode($structure) as $uupart) { @@ -306,7 +306,7 @@ class rcube_message foreach ($structure->parts as $p => $sub_part) { $sub_mimetype = $sub_part->mimetype; - + // check if sub part is if ($sub_mimetype == 'text/plain') $plain_part = $p; @@ -323,7 +323,7 @@ class rcube_message $this->parse_alternative = true; $this->parse_structure($structure->parts[$related_part], true); $this->parse_alternative = false; - + // if plain part was found, we should unset it if html is preferred if ($this->opt['prefer_html'] && count($this->parts)) $plain_part = null; @@ -432,7 +432,7 @@ class rcube_message $this->attachments[] = $mail_part; } // part message/* - else if ($primary_type=='message') { + else if ($primary_type == 'message') { $this->parse_structure($mail_part, true); // list as attachment as well (mostly .eml) @@ -496,6 +496,10 @@ class rcube_message $this->attachments[] = $mail_part; } } + // attachment part as message/rfc822 (#1488026) + else if ($mail_part->mimetype == 'message/rfc822') { + $this->parse_structure($mail_part); + } } // if this was a related part try to resolve references diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php index cfba7fa78..0e38a3101 100644 --- a/program/include/rcube_plugin_api.php +++ b/program/include/rcube_plugin_api.php @@ -176,7 +176,7 @@ class rcube_plugin_api if (is_subclass_of($plugin, 'rcube_plugin')) { // ... task, request type and framed mode if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) - && (!$plugin->noajax || is_a($rcmail->output, 'rcube_template')) + && (!$plugin->noajax || (is_object($rcmail->output) && is_a($rcmail->output, 'rcube_template'))) && (!$plugin->noframe || empty($_REQUEST['_framed'])) ) { $plugin->init(); diff --git a/program/js/app.js b/program/js/app.js index bbb14e7ad..149736107 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -40,11 +40,6 @@ function rcube_webmail() this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); - // mimetypes supported by the browser (default settings) - this.mimetypes = new Array('text/plain', 'text/html', 'text/xml', - 'image/jpeg', 'image/gif', 'image/png', - 'application/x-javascript', 'application/pdf', 'application/x-shockwave-flash'); - // default environment vars this.env.keep_alive = 60; // seconds this.env.request_timeout = 180; // seconds @@ -90,12 +85,15 @@ function rcube_webmail() if (over) button_prop.over = over; this.buttons[command].push(button_prop); + + if (this.loaded) + init_button(command, button_prop); }; // register a specific gui object this.gui_object = function(name, id) { - this.gui_objects[name] = id; + this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id; }; // register a container object @@ -748,7 +746,7 @@ function rcube_webmail() var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part; // open attachment in frame if it's of a supported mimetype - if (this.env.uid && props.mimetype && $.inArray(props.mimetype, this.mimetypes)>=0) { + if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes)>=0) { if (props.mimetype == 'text/html') qstring += '&_safe=1'; this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'); @@ -3505,7 +3503,7 @@ function rcube_webmail() case 27: // escape this.ksearch_hide(); - break; + return; case 37: // left case 39: // right @@ -4914,6 +4912,34 @@ function rcube_webmail() /********* GUI functionality *********/ /*********************************************************/ + var init_button = function(cmd, prop) + { + var elm = document.getElementById(prop.id); + if (!elm) + return; + + var preload = false; + if (prop.type == 'image') { + elm = elm.parentNode; + preload = true; + } + + elm._command = cmd; + elm._id = prop.id; + if (prop.sel) { + elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); }; + elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); }; + if (preload) + new Image().src = prop.sel; + } + if (prop.over) { + elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); }; + elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); }; + if (preload) + new Image().src = prop.over; + } + }; + // enable/disable buttons for page shifting this.set_page_buttons = function() { @@ -4929,31 +4955,7 @@ function rcube_webmail() continue; for (var i=0; i< this.buttons[cmd].length; i++) { - var prop = this.buttons[cmd][i]; - var elm = document.getElementById(prop.id); - if (!elm) - continue; - - var preload = false; - if (prop.type == 'image') { - elm = elm.parentNode; - preload = true; - } - - elm._command = cmd; - elm._id = prop.id; - if (prop.sel) { - elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); }; - elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); }; - if (preload) - new Image().src = prop.sel; - } - if (prop.over) { - elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); }; - elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); }; - if (preload) - new Image().src = prop.over; - } + init_button(cmd, this.buttons[cmd][i]); } } }; @@ -5566,7 +5568,7 @@ function rcube_webmail() var base = this.env.comm_path; // overwrite task name - if (query._action.match(/([a-z]+)\/([a-z-_]+)/)) { + if (query._action.match(/([a-z]+)\/([a-z-_.]+)/)) { query._action = RegExp.$2; base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index b04628fa4..0c19b661b 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1448,7 +1448,7 @@ function rcmail_send_mdn($message, &$smtp_error) { global $RCMAIL, $IMAP; - if (!is_a($message, rcube_message)) + if (!is_object($message) || !is_a($message, rcube_message)) $message = new rcube_message($message); if ($message->headers->mdn_to && !$message->headers->mdn_sent && diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 9138554f7..4eccd287e 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -79,10 +79,23 @@ else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { $ctype_secondary = strtolower($part->ctype_secondary); $mimetype = sprintf('%s/%s', $ctype_primary, $ctype_secondary); + // allow post-processing of the message body + $plugin = $RCMAIL->plugins->exec_hook('message_part_get', + array('id' => $part->mime_id, 'mimetype' => $mimetype, 'part' => $part, 'download' => !empty($_GET['_download']))); + + if ($plugin['abort']) + exit; + + // overwrite modified vars from plugin + $mimetype = $plugin['mimetype']; + list($ctype_primary, $ctype_secondary) = explode('/', $mimetype); + if ($plugin['body']) + $part->body = $plugin['body']; + $browser = $RCMAIL->output->browser; // send download headers - if ($_GET['_download']) { + if ($plugin['download']) { header("Content-Type: application/octet-stream"); if ($browser->ie) header("Content-Type: application/force-download"); @@ -97,7 +110,7 @@ else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { } // deliver part content - if ($ctype_primary == 'text' && $ctype_secondary == 'html' && empty($_GET['_download'])) { + if ($ctype_primary == 'text' && $ctype_secondary == 'html' && empty($plugin['download'])) { // get part body if not available if (!$part->body) $part->body = $MESSAGE->get_part_content($part->mime_id); @@ -119,7 +132,7 @@ else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { else $filename = addcslashes($filename, '"'); - $disposition = !empty($_GET['_download']) ? 'attachment' : 'inline'; + $disposition = !empty($plugin['download']) ? 'attachment' : 'inline'; header("Content-Disposition: $disposition; filename=\"$filename\""); diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 3c2fa238e..1472a9e61 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -52,6 +52,10 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) { $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter()); $OUTPUT->set_env('mailbox', $mbox_name); + // mimetypes supported by the browser (default settings) + $mimetypes = $RCMAIL->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash'); + $OUTPUT->set_env('mimetypes', is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes); + if ($CONFIG['trash_mbox']) $OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']); if ($CONFIG['flag_for_deletion']) diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index ac1cfb3d0..208874f5d 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -141,7 +141,7 @@ switch ($CURR_SECTION) if (isset($CONFIG['max_pagesize']) && ($a_user_prefs['pagesize'] > $CONFIG['max_pagesize'])) $a_user_prefs['pagesize'] = (int) $CONFIG['max_pagesize']; - $a_user_prefs['timezone'] = $_SESSION['timezone'] = (string) $a_user_prefs['timezone']; + $a_user_prefs['timezone'] = (string) $a_user_prefs['timezone']; break; case 'mailbox': diff --git a/skins/default/common.css b/skins/default/common.css index 55d537cc4..f5b1d3de9 100644 --- a/skins/default/common.css +++ b/skins/default/common.css @@ -98,6 +98,13 @@ input.button:hover color: black; } +input.button[disabled], +input.button[disabled]:hover +{ + color: #aaa; + border-color: #ccc; +} + input.mainaction { font-weight: bold;