Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1842511
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
121 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index 578397612..88d8282c7 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -1,418 +1,418 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2011-2013, Kolab Systems AG |
| Copyright (C) 2008-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Spellchecking using different backends |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Helper class for spellchecking with Googielspell and PSpell support.
*
* @package Framework
* @subpackage Utils
*/
class rcube_spellchecker
{
private $matches = array();
private $engine;
private $backend;
private $lang;
private $rc;
private $error;
private $options = array();
private $dict;
private $have_dict;
/**
* Constructor
*
* @param string $lang Language code
*/
function __construct($lang = 'en')
{
$this->rc = rcube::get_instance();
$this->engine = $this->rc->config->get('spellcheck_engine', 'googie');
$this->lang = $lang ?: 'en';
$this->options = array(
'ignore_syms' => $this->rc->config->get('spellcheck_ignore_syms'),
'ignore_nums' => $this->rc->config->get('spellcheck_ignore_nums'),
'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'),
'dictionary' => $this->rc->config->get('spellcheck_dictionary'),
);
$cls = 'rcube_spellchecker_' . $this->engine;
if (class_exists($cls)) {
$this->backend = new $cls($this, $this->lang);
$this->backend->options = $this->options;
}
else {
$this->error = "Unknown spellcheck engine '$this->engine'";
}
}
/**
* Return a list of supported languages
*/
function languages()
{
// trust configuration
$configured = $this->rc->config->get('spellcheck_languages');
if (!empty($configured) && is_array($configured) && !$configured[0]) {
return $configured;
}
else if (!empty($configured)) {
$langs = (array)$configured;
}
else if ($this->backend) {
$langs = $this->backend->languages();
}
// load index
@include(RCUBE_LOCALIZATION_DIR . 'index.inc');
// add correct labels
$languages = array();
- foreach ($langs as $lang) {
+ foreach ((array) $langs as $lang) {
$langc = strtolower(substr($lang, 0, 2));
$alias = $rcube_language_aliases[$langc];
if (!$alias) {
$alias = $langc.'_'.strtoupper($langc);
}
if ($rcube_languages[$lang]) {
$languages[$lang] = $rcube_languages[$lang];
}
else if ($rcube_languages[$alias]) {
$languages[$lang] = $rcube_languages[$alias];
}
else {
$languages[$lang] = ucfirst($lang);
}
}
// remove possible duplicates (#1489395)
$languages = array_unique($languages);
asort($languages);
return $languages;
}
/**
* Set content and check spelling
*
* @param string $text Text content for spellchecking
* @param bool $is_html Enables HTML-to-Text conversion
*
* @return bool True when no mispelling found, otherwise false
*/
function check($text, $is_html = false)
{
// convert to plain text
if ($is_html) {
$this->content = $this->html2text($text);
}
else {
$this->content = $text;
}
if ($this->backend) {
$this->matches = $this->backend->check($this->content);
}
return $this->found() == 0;
}
/**
* Number of mispellings found (after check)
*
* @return int Number of mispellings
*/
function found()
{
return count($this->matches);
}
/**
* Returns suggestions for the specified word
*
* @param string $word The word
*
* @return array Suggestions list
*/
function get_suggestions($word)
{
if ($this->backend) {
return $this->backend->get_suggestions($word);
}
return array();
}
/**
* Returns misspelled words
*
* @param string $text The content for spellchecking. If empty content
* used for check() method will be used.
*
* @return array List of misspelled words
*/
function get_words($text = null, $is_html=false)
{
if ($is_html) {
$text = $this->html2text($text);
}
if ($this->backend) {
return $this->backend->get_words($text);
}
return array();
}
/**
* Returns checking result in XML (Googiespell) format
*
* @return string XML content
*/
function get_xml()
{
// send output
$out = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
foreach ((array)$this->matches as $item) {
$out .= '<c o="'.$item[1].'" l="'.$item[2].'">';
$out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
$out .= '</c>';
}
$out .= '</spellresult>';
return $out;
}
/**
* Returns checking result (misspelled words with suggestions)
*
* @return array Spellchecking result. An array indexed by word.
*/
function get()
{
$result = array();
foreach ((array)$this->matches as $item) {
if ($this->engine == 'pspell') {
$word = $item[0];
}
else {
$word = mb_substr($this->content, $item[1], $item[2], RCUBE_CHARSET);
}
if (is_array($item[4])) {
$suggestions = $item[4];
}
else if (empty($item[4])) {
$suggestions = array();
}
else {
$suggestions = explode("\t", $item[4]);
}
$result[$word] = $suggestions;
}
return $result;
}
/**
* Returns error message
*
* @return string Error message
*/
function error()
{
return $this->error ?: ($this->backend ? $this->backend->error() : false);
}
private function html2text($text)
{
$h2t = new rcube_html2text($text, false, false, 0);
return $h2t->get_text();
}
/**
* Check if the specified word is an exception according to
* spellcheck options.
*
* @param string $word The word
*
* @return bool True if the word is an exception, False otherwise
*/
public function is_exception($word)
{
// Contain only symbols (e.g. "+9,0", "2:2")
if (!$word || preg_match('/^[0-9@#$%^&_+~*<>=:;?!,.-]+$/', $word))
return true;
// Contain symbols (e.g. "g@@gle"), all symbols excluding separators
if (!empty($this->options['ignore_syms']) && preg_match('/[@#$%^&_+~*=-]/', $word))
return true;
// Contain numbers (e.g. "g00g13")
if (!empty($this->options['ignore_nums']) && preg_match('/[0-9]/', $word))
return true;
// Blocked caps (e.g. "GOOGLE")
if (!empty($this->options['ignore_caps']) && $word == mb_strtoupper($word))
return true;
// Use exceptions from dictionary
if (!empty($this->options['dictionary'])) {
$this->load_dict();
// @TODO: should dictionary be case-insensitive?
if (!empty($this->dict) && in_array($word, $this->dict))
return true;
}
return false;
}
/**
* Add a word to dictionary
*
* @param string $word The word to add
*/
public function add_word($word)
{
$this->load_dict();
foreach (explode(' ', $word) as $word) {
// sanity check
if (strlen($word) < 512) {
$this->dict[] = $word;
$valid = true;
}
}
if ($valid) {
$this->dict = array_unique($this->dict);
$this->update_dict();
}
}
/**
* Remove a word from dictionary
*
* @param string $word The word to remove
*/
public function remove_word($word)
{
$this->load_dict();
if (($key = array_search($word, $this->dict)) !== false) {
unset($this->dict[$key]);
$this->update_dict();
}
}
/**
* Update dictionary row in DB
*/
private function update_dict()
{
if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
$userid = $this->rc->get_user_id();
}
$plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
'userid' => $userid, 'language' => $this->lang, 'dictionary' => $this->dict));
if (!empty($plugin['abort'])) {
return;
}
if ($this->have_dict) {
if (!empty($this->dict)) {
$this->rc->db->query(
"UPDATE " . $this->rc->db->table_name('dictionary', true)
." SET `data` = ?"
." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
." AND `language` = ?",
implode(' ', $plugin['dictionary']), $plugin['language']);
}
// don't store empty dict
else {
$this->rc->db->query(
"DELETE FROM " . $this->rc->db->table_name('dictionary', true)
." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
." AND `language` = ?",
$plugin['language']);
}
}
else if (!empty($this->dict)) {
$this->rc->db->query(
"INSERT INTO " . $this->rc->db->table_name('dictionary', true)
." (`user_id`, `language`, `data`) VALUES (?, ?, ?)",
$plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
}
}
/**
* Get dictionary from DB
*/
private function load_dict()
{
if (is_array($this->dict)) {
return $this->dict;
}
if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
$userid = $this->rc->get_user_id();
}
$plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
'userid' => $userid, 'language' => $this->lang, 'dictionary' => array()));
if (empty($plugin['abort'])) {
$dict = array();
$sql_result = $this->rc->db->query(
"SELECT `data` FROM " . $this->rc->db->table_name('dictionary', true)
." WHERE `user_id` ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
." AND `language` = ?",
$plugin['language']);
if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) {
$this->have_dict = true;
if (!empty($sql_arr['data'])) {
$dict = explode(' ', $sql_arr['data']);
}
}
$plugin['dictionary'] = array_merge((array)$plugin['dictionary'], $dict);
}
if (!empty($plugin['dictionary']) && is_array($plugin['dictionary'])) {
$this->dict = $plugin['dictionary'];
}
else {
$this->dict = array();
}
return $this->dict;
}
}
diff --git a/program/lib/Roundcube/spellchecker/enchant.php b/program/lib/Roundcube/spellchecker/enchant.php
index b20e2b49d..0b6dab9e9 100644
--- a/program/lib/Roundcube/spellchecker/enchant.php
+++ b/program/lib/Roundcube/spellchecker/enchant.php
@@ -1,181 +1,185 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 2011-2013, Kolab Systems AG |
| Copyright (C) 20011-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Spellchecking backend implementation to work with Enchant |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
/**
* Spellchecking backend implementation to work with Pspell
*
* @package Framework
* @subpackage Utils
*/
class rcube_spellchecker_enchant extends rcube_spellchecker_engine
{
private $enchant_broker;
private $enchant_dictionary;
private $matches = array();
/**
* Return a list of languages supported by this backend
*
* @see rcube_spellchecker_engine::languages()
*/
function languages()
{
$this->init();
+ if (!$this->enchant_broker) {
+ return;
+ }
+
$langs = array();
if ($dicts = enchant_broker_list_dicts($this->enchant_broker)) {
foreach ($dicts as $dict) {
$langs[] = preg_replace('/-.*$/', '', $dict['lang_tag']);
}
}
return array_unique($langs);
}
/**
* Initializes Enchant dictionary
*/
private function init()
{
if (!$this->enchant_broker) {
if (!extension_loaded('enchant')) {
$this->error = "Enchant extension not available";
return;
}
$this->enchant_broker = enchant_broker_init();
}
if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) {
$this->error = "Unable to load dictionary for selected language using Enchant";
return;
}
$this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang);
}
/**
* Set content and check spelling
*
* @see rcube_spellchecker_engine::check()
*/
function check($text)
{
$this->init();
if (!$this->enchant_dictionary) {
return array();
}
// tokenize
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
$diff = 0;
$matches = array();
foreach ($text as $w) {
$word = trim($w[0]);
$pos = $w[1] - $diff;
$len = mb_strlen($word);
// skip exceptions
if ($this->dictionary->is_exception($word)) {
}
else if (!enchant_dict_check($this->enchant_dictionary, $word)) {
$suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
if (is_array($suggestions) && count($suggestions) > self::MAX_SUGGESTIONS) {
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
}
$matches[] = array($word, $pos, $len, null, $suggestions);
}
$diff += (strlen($word) - $len);
}
$this->matches = $matches;
return $matches;
}
/**
* Returns suggestions for the specified word
*
* @see rcube_spellchecker_engine::get_words()
*/
function get_suggestions($word)
{
$this->init();
if (!$this->enchant_dictionary) {
return array();
}
$suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
if (is_array($suggestions) && count($suggestions) > self::MAX_SUGGESTIONS)
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
return is_array($suggestions) ? $suggestions : array();
}
/**
* Returns misspelled words
*
* @see rcube_spellchecker_engine::get_suggestions()
*/
function get_words($text = null)
{
$result = array();
if ($text) {
// init spellchecker
$this->init();
if (!$this->enchant_dictionary) {
return array();
}
// With Enchant we don't need to get suggestions to return misspelled words
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
foreach ($text as $w) {
$word = trim($w[0]);
// skip exceptions
if ($this->dictionary->is_exception($word)) {
continue;
}
if (!enchant_dict_check($this->enchant_dictionary, $word)) {
$result[] = $word;
}
}
return $result;
}
foreach ($this->matches as $m) {
$result[] = $m[0];
}
return $result;
}
}
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 6ae288aa7..76e4486af 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1,1396 +1,1414 @@
<?php
/**
+-----------------------------------------------------------------------+
| program/steps/mail/compose.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2017, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Compose a new mail message with all headers and attachments |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
$COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
$COMPOSE = null;
if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID]) {
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
}
// give replicated session storage some time to synchronize
$retries = 0;
while ($COMPOSE_ID && !is_array($COMPOSE) && $RCMAIL->db->is_replicated() && $retries++ < 5) {
usleep(500000);
$RCMAIL->session->reload();
if ($_SESSION['compose_data_'.$COMPOSE_ID]) {
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
}
}
// Nothing below is called during message composition, only at "new/forward/reply/draft" initialization or
// if a compose-ID is given (i.e. when the compose step is opened in a new window/tab).
if (!is_array($COMPOSE)) {
// Infinite redirect prevention in case of broken session (#1487028)
if ($COMPOSE_ID) {
// if we know the message with specified ID was already sent
// we can ignore the error and compose a new message (#1490009)
if ($COMPOSE_ID != $_SESSION['last_compose_session']) {
rcube::raise_error(array('code' => 450), false, true);
}
}
$COMPOSE_ID = uniqid(mt_rand());
$params = rcube_utils::request2param(rcube_utils::INPUT_GET, 'task|action', true);
$_SESSION['compose_data_'.$COMPOSE_ID] = array(
'id' => $COMPOSE_ID,
'param' => $params,
'mailbox' => $params['mbox'] ?: $RCMAIL->storage->get_folder(),
);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
rcmail_process_compose_params($COMPOSE);
// check if folder for saving sent messages exists and is subscribed (#1486802)
if ($sent_folder = $COMPOSE['param']['sent_mbox']) {
rcmail_sendmail::check_sent_folder($sent_folder, true);
}
// redirect to a unique URL with all parameters stored in session
$OUTPUT->redirect(array(
'_action' => 'compose',
'_id' => $COMPOSE['id'],
'_search' => $_REQUEST['_search'],
));
}
// add some labels to client
$OUTPUT->add_label('notuploadedwarning', 'savingmessage', 'siginserted', 'responseinserted',
'messagesaved', 'converting', 'editorwarning', 'discard',
'fileuploaderror', 'sendmessage', 'newresponse', 'responsename', 'responsetext', 'save',
'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore',
'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender',
'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys',
'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired',
'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching', 'namex', 'attachmentrename'
);
$OUTPUT->set_pagetitle($RCMAIL->gettext('compose'));
$OUTPUT->set_env('compose_id', $COMPOSE['id']);
$OUTPUT->set_env('session_id', session_id());
$OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder());
$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0);
$OUTPUT->set_env('sig_below', $RCMAIL->config->get('sig_below'));
$OUTPUT->set_env('save_localstorage', (bool)$RCMAIL->config->get('compose_save_localstorage'));
$OUTPUT->set_env('is_sent', false);
$OUTPUT->set_env('mimetypes', rcmail_supported_mimetypes());
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
$config_show_sig = $RCMAIL->config->get('show_sig', 1);
// add config parameters to client script
if (strlen($drafts_mbox)) {
$OUTPUT->set_env('drafts_mailbox', $drafts_mbox);
$OUTPUT->set_env('draft_autosave', $RCMAIL->config->get('draft_autosave'));
}
// default font for HTML editor
$font = rcmail::font_defs($RCMAIL->config->get('default_font'));
if ($font && !is_array($font)) {
$OUTPUT->set_env('default_font', $font);
}
// default font size for HTML editor
if ($font_size = $RCMAIL->config->get('default_font_size')) {
$OUTPUT->set_env('default_font_size', $font_size);
}
// get reference message and set compose mode
if ($msg_uid = $COMPOSE['param']['draft_uid']) {
$compose_mode = rcmail_sendmail::MODE_DRAFT;
$OUTPUT->set_env('draft_id', $msg_uid);
$RCMAIL->storage->set_folder($drafts_mbox);
}
else if ($msg_uid = $COMPOSE['param']['reply_uid']) {
$compose_mode = rcmail_sendmail::MODE_REPLY;
}
else if ($msg_uid = $COMPOSE['param']['forward_uid']) {
$compose_mode = rcmail_sendmail::MODE_FORWARD;
$COMPOSE['forward_uid'] = $msg_uid;
$COMPOSE['as_attachment'] = !empty($COMPOSE['param']['attachment']);
}
else if ($msg_uid = $COMPOSE['param']['uid']) {
$compose_mode = rcmail_sendmail::MODE_EDIT;
}
if ($compose_mode) {
$COMPOSE['mode'] = $compose_mode;
$OUTPUT->set_env('compose_mode', $compose_mode);
}
if ($compose_mode == rcmail_sendmail::MODE_EDIT || $compose_mode == rcmail_sendmail::MODE_DRAFT) {
// don't add signature in draft/edit mode, we'll also not remove the old-one
// but only on page display, later we should be able to change identity/sig (#1489229)
if ($config_show_sig == 1 || $config_show_sig == 2) {
$OUTPUT->set_env('show_sig_later', true);
}
}
else if ($config_show_sig == 1)
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 2 && empty($compose_mode))
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 3 && ($compose_mode == rcmail_sendmail::MODE_REPLY || $compose_mode == rcmail_sendmail::MODE_FORWARD))
$OUTPUT->set_env('show_sig', true);
// set line length for body wrapping
$LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) {
$mbox_name = $RCMAIL->storage->get_folder();
// set format before rcube_message construction
// use the same format as for the message view
if (isset($_SESSION['msg_formats'][$mbox_name.':'.$msg_uid])) {
$RCMAIL->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$msg_uid]);
}
else {
$prefer_html = $RCMAIL->config->get('prefer_html') || $RCMAIL->config->get('htmleditor')
|| $compose_mode == rcmail_sendmail::MODE_DRAFT || $compose_mode == rcmail_sendmail::MODE_EDIT;
$RCMAIL->config->set('prefer_html', $prefer_html);
}
$MESSAGE = new rcube_message($msg_uid);
// make sure message is marked as read
if ($MESSAGE->headers && $MESSAGE->context === null && empty($MESSAGE->headers->flags['SEEN'])) {
$RCMAIL->storage->set_flag($msg_uid, 'SEEN');
}
if (!empty($MESSAGE->headers->charset)) {
$RCMAIL->storage->set_charset($MESSAGE->headers->charset);
}
if (!$MESSAGE->headers) {
// error
}
else if ($compose_mode == rcmail_sendmail::MODE_FORWARD || $compose_mode == rcmail_sendmail::MODE_REPLY) {
if ($compose_mode == rcmail_sendmail::MODE_REPLY) {
$COMPOSE['reply_uid'] = $MESSAGE->context === null ? $msg_uid : null;
if (!empty($COMPOSE['param']['all'])) {
$MESSAGE->reply_all = $COMPOSE['param']['all'];
}
}
else {
$COMPOSE['forward_uid'] = $msg_uid;
}
$COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID;
$COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID);
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $COMPOSE['mailbox'])
&& rcmail_sendmail::check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
else if ($compose_mode == rcmail_sendmail::MODE_DRAFT || $compose_mode == rcmail_sendmail::MODE_EDIT) {
if ($compose_mode == rcmail_sendmail::MODE_DRAFT) {
if ($draft_info = $MESSAGE->headers->get('x-draft-info')) {
// get reply_uid/forward_uid to flag the original message when sending
$info = rcmail_sendmail::draftinfo_decode($draft_info);
if ($info['type'] == 'reply')
$COMPOSE['reply_uid'] = $info['uid'];
else if ($info['type'] == 'forward')
$COMPOSE['forward_uid'] = $info['uid'];
$COMPOSE['mailbox'] = $info['folder'];
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $info['folder'])
&& rcmail_sendmail::check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
if (($msgid = $MESSAGE->headers->get('message-id')) && !preg_match('/^mid:[0-9]+$/', $msgid)) {
$COMPOSE['param']['message-id'] = $msgid;
}
// use message UID as draft_id
$OUTPUT->set_env('draft_id', $msg_uid);
}
if ($in_reply_to = $MESSAGE->headers->get('in-reply-to')) {
$COMPOSE['reply_msgid'] = '<' . $in_reply_to . '>';
}
$COMPOSE['references'] = $MESSAGE->headers->references;
}
}
else {
$MESSAGE = new stdClass();
// apply mailto: URL parameters
if (!empty($COMPOSE['param']['in-reply-to'])) {
$COMPOSE['reply_msgid'] = '<' . $COMPOSE['param']['in-reply-to'] . '>';
}
if (!empty($COMPOSE['param']['references'])) {
$COMPOSE['references'] = $COMPOSE['param']['references'];
}
}
if (!empty($COMPOSE['reply_msgid'])) {
$OUTPUT->set_env('reply_msgid', $COMPOSE['reply_msgid']);
}
// Initialize helper class to build the UI
$SENDMAIL = new rcmail_sendmail($COMPOSE, array('message' => $MESSAGE));
// process $MESSAGE body/attachments, set $MESSAGE_BODY/$HTML_MODE vars and some session data
$MESSAGE_BODY = rcmail_prepare_message_body();
// register UI objects (Note: some objects are registered by rcmail_sendmail above)
$OUTPUT->add_handlers(array(
'composebody' => 'rcmail_compose_body',
'composeattachmentlist' => 'rcmail_compose_attachment_list',
'composeattachmentform' => 'rcmail_compose_attachment_form',
'composeattachment' => 'rcmail_compose_attachment_field',
'filedroparea' => 'rcmail_compose_file_drop_area',
'editorselector' => 'rcmail_editor_selector',
'addressbooks' => 'rcmail_addressbook_list',
'addresslist' => 'rcmail_contacts_list',
'responseslist' => 'rcmail_compose_responses_list',
));
$OUTPUT->include_script('publickey.js');
+rcmail_spellchecker_init();
+
$OUTPUT->send('compose');
/****** compose mode functions ********/
// process compose request parameters
function rcmail_process_compose_params(&$COMPOSE)
{
if ($COMPOSE['param']['to']) {
$mailto = explode('?', $COMPOSE['param']['to'], 2);
// #1486037: remove "mailto:" prefix
$COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $mailto[0]);
// #1490346: decode the recipient address
// #1490510: use raw encoding for correct "+" character handling as specified in RFC6068
$COMPOSE['param']['to'] = rawurldecode($COMPOSE['param']['to']);
// Supported case-insensitive tokens in mailto URL
$url_tokens = array('to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'body');
if (!empty($mailto[1])) {
parse_str($mailto[1], $query);
foreach ($query as $f => $val) {
if (($key = array_search(strtolower($f), $url_tokens)) !== false) {
$f = $url_tokens[$key];
}
// merge mailto: addresses with addresses from 'to' parameter
if ($f == 'to' && !empty($COMPOSE['param']['to'])) {
$to_addresses = rcube_mime::decode_address_list($COMPOSE['param']['to'], null, true, null, true);
$add_addresses = rcube_mime::decode_address_list($val, null, true);
foreach ($add_addresses as $addr) {
if (!in_array($addr['mailto'], $to_addresses)) {
$to_addresses[] = $addr['mailto'];
$COMPOSE['param']['to'] = (!empty($to_addresses) ? ', ' : '') . $addr['string'];
}
}
}
else {
$COMPOSE['param'][$f] = $val;
}
}
}
}
// resolve _forward_uid=* to an absolute list of messages from a search result
if ($COMPOSE['param']['forward_uid'] == '*' && is_object($_SESSION['search'][1])) {
$COMPOSE['param']['forward_uid'] = $_SESSION['search'][1]->get();
}
// clean HTML message body which can be submitted by URL
if (!empty($COMPOSE['param']['body'])) {
if ($COMPOSE['param']['html'] = strpos($COMPOSE['param']['body'], '<') !== false) {
$wash_params = array('safe' => false, 'inline_html' => true);
$COMPOSE['param']['body'] = rcmail_wash_html($COMPOSE['param']['body'], $wash_params, array());
$COMPOSE['param']['body'] = preg_replace('/<!--[^>\n]+>/', '', $COMPOSE['param']['body']);
$COMPOSE['param']['body'] = preg_replace('/<\/?body>/', '', $COMPOSE['param']['body']);
}
}
$RCMAIL = rcmail::get_instance();
// select folder where to save the sent message
$COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox');
// pipe compose parameters thru plugins
$plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE);
$COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']);
// add attachments listed by message_compose hook
if (is_array($plugin['attachments'])) {
foreach ($plugin['attachments'] as $attach) {
// we have structured data
if (is_array($attach)) {
$attachment = $attach + array('group' => $COMPOSE_ID);
}
// only a file path is given
else {
$filename = basename($attach);
$attachment = array(
'group' => $COMPOSE_ID,
'name' => $filename,
'mimetype' => rcube_mime::file_content_type($attach, $filename),
'size' => filesize($attach),
'path' => $attach,
);
}
// save attachment if valid
if (($attachment['data'] && $attachment['name']) || ($attachment['path'] && file_exists($attachment['path']))) {
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
}
if ($attachment['status'] && !$attachment['abort']) {
unset($attachment['data'], $attachment['status'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
}
}
}
function rcmail_compose_editor_mode()
{
global $RCMAIL, $COMPOSE;
static $useHtml;
if ($useHtml !== null) {
return $useHtml;
}
$html_editor = intval($RCMAIL->config->get('htmleditor'));
$compose_mode = $COMPOSE['mode'];
if (is_bool($COMPOSE['param']['html'])) {
$useHtml = $COMPOSE['param']['html'];
}
else if (isset($_POST['_is_html'])) {
$useHtml = !empty($_POST['_is_html']);
}
else if ($compose_mode == rcmail_sendmail::MODE_DRAFT || $compose_mode == rcmail_sendmail::MODE_EDIT) {
$useHtml = rcmail_message_is_html();
}
else if ($compose_mode == rcmail_sendmail::MODE_REPLY) {
$useHtml = $html_editor == 1 || ($html_editor >= 2 && rcmail_message_is_html());
}
else if ($compose_mode == rcmail_sendmail::MODE_FORWARD) {
$useHtml = $html_editor == 1 || $html_editor == 4
|| ($html_editor == 3 && rcmail_message_is_html());
}
else {
$useHtml = $html_editor == 1 || $html_editor == 4;
}
return $useHtml;
}
function rcmail_message_is_html()
{
global $RCMAIL, $MESSAGE;
return $RCMAIL->config->get('prefer_html') && ($MESSAGE instanceof rcube_message) && $MESSAGE->has_html_part(true);
}
+function rcmail_spellchecker_init()
+{
+ global $RCMAIL, $OUTPUT;
+
+ // Set language list
+ if ($RCMAIL->config->get('enable_spellcheck')) {
+ $spellchecker = new rcube_spellchecker();
+ $spellcheck_langs = $spellchecker->languages();
+ }
+
+ if (!empty($spellchecker) && empty($spellcheck_langs)) {
+ if ($err = $spellchecker->error()) {
+ rcube::raise_error(array('code' => 500,
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Spell check engine error: " . trim($err)),
+ true, false);
+ }
+ }
+ else if (!empty($spellchecker)) {
+ $dictionary = (bool) $RCMAIL->config->get('spellcheck_dictionary');
+ $lang = $_SESSION['language'];
+
+ // if not found in the list, try with two-letter code
+ if (!$spellcheck_langs[$lang]) {
+ $lang = strtolower(substr($lang, 0, 2));
+ }
+
+ if (!$spellcheck_langs[$lang]) {
+ $lang = 'en';
+ }
+
+ $editor_lang_set = array();
+ foreach ($spellcheck_langs as $key => $name) {
+ $editor_lang_set[] = ($key == $lang ? '+' : '') . rcube::JQ($name).'='.rcube::JQ($key);
+ }
+
+ // include GoogieSpell
+ $OUTPUT->include_script('googiespell.js');
+ $OUTPUT->add_script(sprintf(
+ "var googie = new GoogieSpell('%s/images/googiespell/','%s&lang=', %s);\n".
+ "googie.lang_chck_spell = \"%s\";\n".
+ "googie.lang_rsm_edt = \"%s\";\n".
+ "googie.lang_close = \"%s\";\n".
+ "googie.lang_revert = \"%s\";\n".
+ "googie.lang_no_error_found = \"%s\";\n".
+ "googie.lang_learn_word = \"%s\";\n".
+ "googie.setLanguages(%s);\n".
+ "googie.setCurrentLanguage('%s');\n".
+ "googie.setDecoration(false);\n".
+ "googie.decorateTextarea(rcmail.env.composebody);\n",
+ $RCMAIL->output->asset_url($RCMAIL->output->get_skin_path()),
+ $RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)),
+ !empty($dictionary) ? 'true' : 'false',
+ rcube::JQ(rcube::Q($RCMAIL->gettext('checkspelling'))),
+ rcube::JQ(rcube::Q($RCMAIL->gettext('resumeediting'))),
+ rcube::JQ(rcube::Q($RCMAIL->gettext('close'))),
+ rcube::JQ(rcube::Q($RCMAIL->gettext('revertto'))),
+ rcube::JQ(rcube::Q($RCMAIL->gettext('nospellerrors'))),
+ rcube::JQ(rcube::Q($RCMAIL->gettext('addtodict'))),
+ rcube_output::json_serialize($spellcheck_langs),
+ $lang
+ ), 'foot');
+
+ $OUTPUT->add_label('checking');
+ $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set));
+ $OUTPUT->set_env('spell_langs', $spellcheck_langs);
+ $OUTPUT->set_env('spell_lang', $lang);
+ }
+}
+
function rcmail_prepare_message_body()
{
global $RCMAIL, $MESSAGE, $COMPOSE, $HTML_MODE;
// use posted message body
if (!empty($_POST['_message'])) {
$body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, true);
$isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
}
else if ($COMPOSE['param']['body']) {
$body = $COMPOSE['param']['body'];
$isHtml = (bool) $COMPOSE['param']['html'];
}
// forward as attachment
else if ($COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD && $COMPOSE['as_attachment']) {
$isHtml = rcmail_compose_editor_mode();
$body = '';
rcmail_write_forward_attachments();
}
// reply/edit/draft/forward
else if ($COMPOSE['mode'] && ($COMPOSE['mode'] != rcmail_sendmail::MODE_REPLY || intval($RCMAIL->config->get('reply_mode')) != -1)) {
$isHtml = rcmail_compose_editor_mode();
$messages = array();
if (!empty($MESSAGE->parts)) {
// collect IDs of message/rfc822 parts
foreach ($MESSAGE->mime_parts() as $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
}
foreach ($MESSAGE->parts as $part) {
if ($part->realtype == 'multipart/encrypted') {
// find the encrypted message payload part
if ($pgp_mime_part = $MESSAGE->get_multipart_encrypted_part()) {
$RCMAIL->output->set_env('pgp_mime_message', array(
'_mbox' => $RCMAIL->storage->get_folder(),
'_uid' => $MESSAGE->uid,
'_part' => $pgp_mime_part->mime_id,
));
}
continue;
}
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || $MESSAGE->is_attachment($part)) {
continue;
}
// skip all content parts inside the message/rfc822 part
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
if ($part_body = rcmail_compose_part_body($part, $isHtml)) {
$body .= ($body ? ($isHtml ? '<br/>' : "\n") : '') . $part_body;
}
}
}
else {
$body = rcmail_compose_part_body($MESSAGE, $isHtml);
}
// compose reply-body
if ($COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
$body = rcmail_create_reply_body($body, $isHtml);
if ($MESSAGE->pgp_mime) {
$RCMAIL->output->set_env('compose_reply_header', rcmail_get_reply_header($MESSAGE));
}
}
// forward message body inline
else if ($COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD) {
$body = rcmail_create_forward_body($body, $isHtml);
}
// load draft message body
else if ($COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT || $COMPOSE['mode'] == rcmail_sendmail::MODE_EDIT) {
$body = rcmail_create_draft_body($body, $isHtml);
}
}
else { // new message
$isHtml = rcmail_compose_editor_mode();
}
$plugin = $RCMAIL->plugins->exec_hook('message_compose_body',
array('body' => $body, 'html' => $isHtml, 'mode' => $COMPOSE['mode']));
$body = $plugin['body'];
unset($plugin);
// add blocked.gif attachment (#1486516)
$regexp = '# src="program/resources/blocked\.gif"#';
if ($isHtml && preg_match($regexp, $body)) {
$content = $RCMAIL->get_resource_content('blocked.gif');
if ($content && ($attachment = rcmail_save_image('blocked.gif', 'image/gif', $content))) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
$body = preg_replace($regexp, ' src="' . $url . '"', $body);
}
}
$HTML_MODE = $isHtml;
return $body;
}
function rcmail_compose_part_body($part, $isHtml = false)
{
global $RCMAIL, $COMPOSE, $MESSAGE, $LINE_LENGTH;
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcube_utils::mem_check($part->size * 10)) {
return '';
}
// fetch part if not available
$body = $MESSAGE->get_part_body($part->mime_id, true);
// message is cached but not exists (#1485443), or other error
if ($body === false) {
return '';
}
// register this part as pgp encrypted
if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) {
$MESSAGE->pgp_mime = true;
$RCMAIL->output->set_env('pgp_mime_message', array(
'_mbox' => $RCMAIL->storage->get_folder(), '_uid' => $MESSAGE->uid, '_part' => $part->mime_id,
));
}
if ($isHtml) {
if ($part->ctype_secondary == 'html') {
}
else if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
}
else {
// try to remove the signature
if ($COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT && $COMPOSE['mode'] != rcmail_sendmail::MODE_EDIT) {
if ($RCMAIL->config->get('strip_existing_sig', true)) {
$body = rcmail_remove_signature($body);
}
}
// add HTML formatting
$body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed', $part->ctype_parameters['delsp'] == 'yes');
}
}
else {
if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
$part->ctype_secondary = 'html';
}
if ($part->ctype_secondary == 'html') {
// use html part if it has been used for message (pre)viewing
// decrease line length for quoting
$len = $COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
$body = $RCMAIL->html2text($body, array('width' => $len));
}
else {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body, null, $part->ctype_parameters['delsp'] == 'yes');
}
// try to remove the signature
if ($COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT && $COMPOSE['mode'] != rcmail_sendmail::MODE_EDIT) {
if ($RCMAIL->config->get('strip_existing_sig', true)) {
$body = rcmail_remove_signature($body);
}
}
}
}
return $body;
}
function rcmail_compose_body($attrib)
{
global $RCMAIL, $OUTPUT, $HTML_MODE, $MESSAGE_BODY, $SENDMAIL;
list($form_start, $form_end) = $SENDMAIL->form_tags($attrib);
unset($attrib['form']);
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmComposeBody';
}
// If desired, set this textarea to be editable by TinyMCE
$attrib['data-html-editor'] = true;
if ($HTML_MODE) {
$attrib['class'] = trim($attrib['class'] . ' mce_editor');
}
$attrib['name'] = '_message';
$textarea = new html_textarea($attrib);
$hidden = new html_hiddenfield();
$hidden->add(array('name' => '_draft_saveid', 'value' => $RCMAIL->output->get_env('draft_id')));
$hidden->add(array('name' => '_draft', 'value' => ''));
$hidden->add(array('name' => '_is_html', 'value' => $HTML_MODE ? "1" : "0"));
$hidden->add(array('name' => '_framed', 'value' => '1'));
$OUTPUT->set_env('composebody', $attrib['id']);
// include HTML editor
$RCMAIL->html_editor();
- // Set language list
- if ($RCMAIL->config->get('enable_spellcheck')) {
- $engine = new rcube_spellchecker();
- $dictionary = (bool) $RCMAIL->config->get('spellcheck_dictionary');
- $spellcheck_langs = $engine->languages();
- $lang = $_SESSION['language'];
-
- // if not found in the list, try with two-letter code
- if (!$spellcheck_langs[$lang]) {
- $lang = strtolower(substr($lang, 0, 2));
- }
-
- if (!$spellcheck_langs[$lang]) {
- $lang = 'en';
- }
-
- $editor_lang_set = array();
- foreach ($spellcheck_langs as $key => $name) {
- $editor_lang_set[] = ($key == $lang ? '+' : '') . rcube::JQ($name).'='.rcube::JQ($key);
- }
-
- // include GoogieSpell
- $OUTPUT->include_script('googiespell.js');
- $OUTPUT->add_script(sprintf(
- "var googie = new GoogieSpell('%s/images/googiespell/','%s&lang=', %s);\n".
- "googie.lang_chck_spell = \"%s\";\n".
- "googie.lang_rsm_edt = \"%s\";\n".
- "googie.lang_close = \"%s\";\n".
- "googie.lang_revert = \"%s\";\n".
- "googie.lang_no_error_found = \"%s\";\n".
- "googie.lang_learn_word = \"%s\";\n".
- "googie.setLanguages(%s);\n".
- "googie.setCurrentLanguage('%s');\n".
- "googie.setDecoration(false);\n".
- "googie.decorateTextarea('%s');\n",
- $RCMAIL->output->asset_url($RCMAIL->output->get_skin_path()),
- $RCMAIL->url(array('_task' => 'utils', '_action' => 'spell', '_remote' => 1)),
- !empty($dictionary) ? 'true' : 'false',
- rcube::JQ(rcube::Q($RCMAIL->gettext('checkspelling'))),
- rcube::JQ(rcube::Q($RCMAIL->gettext('resumeediting'))),
- rcube::JQ(rcube::Q($RCMAIL->gettext('close'))),
- rcube::JQ(rcube::Q($RCMAIL->gettext('revertto'))),
- rcube::JQ(rcube::Q($RCMAIL->gettext('nospellerrors'))),
- rcube::JQ(rcube::Q($RCMAIL->gettext('addtodict'))),
- rcube_output::json_serialize($spellcheck_langs),
- $lang,
- $attrib['id']), 'foot');
-
- $OUTPUT->add_label('checking');
- $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set));
- $OUTPUT->set_env('spell_langs', $spellcheck_langs);
- $OUTPUT->set_env('spell_lang', $lang);
- }
-
return ($form_start ? "$form_start\n" : '')
. "\n" . $hidden->show() . "\n" . $textarea->show($MESSAGE_BODY)
. ($form_end ? "\n$form_end\n" : '');
}
function rcmail_create_reply_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $LINE_LENGTH;
$reply_mode = (int) $RCMAIL->config->get('reply_mode');
$reply_indent = $reply_mode != 2;
// In top-posting without quoting it's better to use multi-line header
if ($reply_mode == 2) {
$prefix = rcmail_get_forward_header($MESSAGE, $bodyIsHtml, false);
}
else {
$prefix = rcmail_get_reply_header($MESSAGE);
if ($bodyIsHtml) {
$prefix = '<p id="reply-intro">' . rcube::Q($prefix) . '</p>';
}
else {
$prefix .= "\n";
}
}
if (!$bodyIsHtml) {
// soft-wrap and quote message text
$body = rcmail_wrap_and_quote($body, $LINE_LENGTH, $reply_indent);
if ($reply_mode > 0) { // top-posting
$prefix = "\n\n\n" . $prefix;
$suffix = '';
}
else {
$suffix = "\n";
}
}
else {
// save inline images to files
$cid_map = rcmail_write_inline_attachments($MESSAGE);
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
$suffix = '';
if ($reply_indent) {
$prefix .= '<blockquote>';
$suffix .= '</blockquote>';
}
if ($reply_mode == 2) {
// top-posting, no indent
}
else if ($reply_mode > 0) {
// top-posting
$prefix = '<br>' . $prefix;
}
else {
$suffix .= '<p><br/></p>';
}
}
return $prefix . $body . $suffix;
}
function rcmail_get_reply_header($message)
{
global $RCMAIL;
$from = array_pop(rcube_mime::decode_address_list($message->get_header('from'), 1, false, $message->headers->charset));
return $RCMAIL->gettext(array(
'name' => 'mailreplyintro',
'vars' => array(
'date' => $RCMAIL->format_date($message->headers->date, $RCMAIL->config->get('date_long')),
'sender' => $from['name'] ?: rcube_utils::idn_to_utf8($from['mailto']),
)
));
}
function rcmail_create_forward_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $COMPOSE;
// add attachments
if (!isset($COMPOSE['forward_attachments']) && is_array($MESSAGE->mime_parts)) {
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
}
if (!$bodyIsHtml) {
$body = trim($body, "\r\n");
}
else {
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
}
return rcmail_get_forward_header($MESSAGE, $bodyIsHtml) . $body;
}
function rcmail_get_forward_header($message, $bodyIsHtml = false, $extended = true)
{
global $RCMAIL;
$date = $RCMAIL->format_date($message->headers->date, $RCMAIL->config->get('date_long'));
if (!$bodyIsHtml) {
$prefix = "\n\n\n-------- " . $RCMAIL->gettext('originalmessage') . " --------\n";
$prefix .= $RCMAIL->gettext('subject') . ': ' . $message->subject . "\n";
$prefix .= $RCMAIL->gettext('date') . ': ' . $date . "\n";
$prefix .= $RCMAIL->gettext('from') . ': ' . $message->get_header('from') . "\n";
$prefix .= $RCMAIL->gettext('to') . ': ' . $message->get_header('to') . "\n";
if ($extended && ($cc = $message->headers->get('cc'))) {
$prefix .= $RCMAIL->gettext('cc') . ': ' . $cc . "\n";
}
if ($extended && ($replyto = $message->headers->get('reply-to')) && $replyto != $message->get_header('from')) {
$prefix .= $RCMAIL->gettext('replyto') . ': ' . $replyto . "\n";
}
$prefix .= "\n";
}
else {
$prefix = sprintf(
"<br /><p>-------- " . $RCMAIL->gettext('originalmessage') . " --------</p>" .
"<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tbody>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('subject'), rcube::Q($message->subject),
$RCMAIL->gettext('date'), rcube::Q($date),
$RCMAIL->gettext('from'), rcube::Q($message->get_header('from'), 'replace'),
$RCMAIL->gettext('to'), rcube::Q($message->get_header('to'), 'replace'));
if ($extended && ($cc = $message->headers->get('cc'))) {
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('cc'), rcube::Q($cc, 'replace'));
}
if ($extended && ($replyto = $message->headers->get('reply-to')) && $replyto != $message->get_header('from')) {
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
$RCMAIL->gettext('replyto'), rcube::Q($replyto, 'replace'));
}
$prefix .= "</tbody></table><br>";
}
return $prefix;
}
function rcmail_create_draft_body($body, $bodyIsHtml)
{
global $MESSAGE, $COMPOSE;
// add attachments
// count($MESSAGE->mime_parts) can be 1 - e.g. attachment, but no text!
if (empty($COMPOSE['forward_attachments'])
&& is_array($MESSAGE->mime_parts)
&& count($MESSAGE->mime_parts) > 0
) {
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
}
// clean up HTML tags - XSS prevention (#1489251)
if ($bodyIsHtml) {
$body = rcmail_wash_html($body, array('safe' => 1), $cid_map);
// cleanup
$body = preg_replace(array(
// remove comments (produced by washtml)
'/<!--[^>]+-->/',
// remove <body> tags
'/<body([^>]*)>/i',
'/<\/body>/i',
// convert TinyMCE's empty-line sequence (#1490463)
'/<p>\xC2\xA0<\/p>/',
),
array(
'',
'',
'',
'<p><br /></p>',
),
$body
);
// replace cid with href in inline images links
if (!empty($cid_map)) {
$body = str_replace(array_keys($cid_map), array_values($cid_map), $body);
}
}
return $body;
}
// Removes signature from the message body
function rcmail_remove_signature($body)
{
global $RCMAIL;
$body = str_replace("\r\n", "\n", $body);
$len = strlen($body);
$sig_max_lines = $RCMAIL->config->get('sig_max_lines', 15);
while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) {
if ($sp == 0 || $body[$sp-1] == "\n") {
// do not touch blocks with more that X lines
if (substr_count($body, "\n", $sp) < $sig_max_lines) {
$body = substr($body, 0, max(0, $sp-1));
}
break;
}
}
return $body;
}
function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
{
global $RCMAIL, $COMPOSE;
$loaded_attachments = array();
foreach ((array)$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
}
$cid_map = array();
$messages = array();
if ($message->pgp_mime) {
return $cid_map;
}
foreach ((array) $message->mime_parts() as $pid => $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) {
// skip parts that aren't valid attachments
if ($part->ctype_primary == 'multipart' || $part->mimetype == 'application/ms-tnef') {
continue;
}
// skip message attachments in reply mode
if ($part->ctype_primary == 'message' && $COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
continue;
}
// skip inline images when forwarding in text mode
if ($part->content_id && $part->disposition == 'inline' && !$bodyIsHtml && $COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD) {
continue;
}
// skip version.txt parts of multipart/encrypted messages
if ($message->pgp_mime && $part->mimetype == 'application/pgp-encrypted' && $part->filename == 'version.txt') {
continue;
}
// skip attachments included in message/rfc822 attachment (#1486487, #1490607)
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
if (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype])
|| ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id']))
) {
if ($bodyIsHtml && ($part->content_id || $part->content_location)) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
}
$COMPOSE['forward_attachments'] = true;
return $cid_map;
}
function rcmail_write_inline_attachments(&$message)
{
global $RCMAIL, $COMPOSE;
$cid_map = array();
$messages = array();
if ($message->pgp_mime) {
return $cid_map;
}
foreach ((array) $message->mime_parts() as $pid => $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
if (($part->content_id || $part->content_location) && $part->filename) {
// skip attachments included in message/rfc822 attachment (#1486487, #1490607)
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
if ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id'])) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
return $cid_map;
}
// Creates attachment(s) from the forwarded message(s)
function rcmail_write_forward_attachments()
{
global $RCMAIL, $COMPOSE, $MESSAGE;
if ($MESSAGE->pgp_mime) {
return;
}
$storage = $RCMAIL->get_storage();
$names = array();
$refs = array();
$size_errors = 0;
$size_limit = parse_bytes($RCMAIL->config->get('max_message_size'));
$total_size = 10 * 1024; // size of message body, to start with
$loaded_attachments = array();
foreach ((array)$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
$total_size += $attachment['size'];
}
if ($COMPOSE['forward_uid'] == '*') {
$index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order());
$COMPOSE['forward_uid'] = $index->get();
}
else if (!is_array($COMPOSE['forward_uid']) && strpos($COMPOSE['forward_uid'], ':')) {
$COMPOSE['forward_uid'] = rcube_imap_generic::uncompressMessageSet($COMPOSE['forward_uid']);
}
else if (is_string($COMPOSE['forward_uid'])) {
$COMPOSE['forward_uid'] = explode(',', $COMPOSE['forward_uid']);
}
foreach ((array)$COMPOSE['forward_uid'] as $uid) {
$message = new rcube_message($uid);
if (empty($message->headers)) {
continue;
}
if (!empty($message->headers->charset)) {
$storage->set_charset($message->headers->charset);
}
if (empty($MESSAGE->subject)) {
$MESSAGE->subject = $message->subject;
}
// generate (unique) attachment name
$name = strlen($message->subject) ? mb_substr($message->subject, 0, 64) : 'message_rfc822';
if (!empty($names[$name])) {
$names[$name]++;
$name .= '_' . $names[$name];
}
$names[$name] = 1;
$name .= '.eml';
if (!empty($loaded_attachments[$name . 'message/rfc822'])) {
continue;
}
if ($size_limit && $size_limit < $total_size + $message->headers->size) {
$size_errors++;
continue;
}
$total_size += $message->headers->size;
rcmail_save_attachment($message, null, $COMPOSE['id'], array('filename' => $name));
if ($message->headers->messageID) {
$refs[] = $message->headers->messageID;
}
}
// set In-Reply-To and References headers
if (count($refs) == 1) {
$COMPOSE['reply_msgid'] = $refs[0];
}
if (!empty($refs)) {
$COMPOSE['references'] = implode(' ', $refs);
}
if ($size_errors) {
$limit = $RCMAIL->show_bytes($size_limit);
$error = $RCMAIL->gettext(array('name' => 'msgsizeerrorfwd', 'vars' => array('num' => $size_errors, 'size' => $limit)));
$RCMAIL->output->add_script(sprintf("%s.display_message('%s', 'error');", rcmail_output::JS_OBJECT_NAME, rcube::JQ($error)), 'docready');
}
}
// Saves an image as attachment
function rcmail_save_image($path, $mimetype = '', $data = null)
{
global $COMPOSE;
// handle attachments in memory
if (empty($data)) {
$data = file_get_contents($path);
$is_file = true;
}
$name = rcmail_basename($path);
if (empty($mimetype)) {
if ($is_file) {
$mimetype = rcube_mime::file_content_type($path, $name);
}
else {
$mimetype = rcube_mime::file_content_type($data, $name, 'application/octet-stream', true);
}
}
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $name,
'mimetype' => $mimetype,
'data' => $data,
'size' => strlen($data),
);
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
return $attachment;
}
return false;
}
// Unicode-safe basename()
function rcmail_basename($filename)
{
// basename() is not unicode safe and locale dependent
if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
return preg_replace('/^.*[\\\\\\/]/', '', $filename);
}
else {
return preg_replace('/^.*[\/]/', '', $filename);
}
}
/**
* Attachments list object for templates
*/
function rcmail_compose_attachment_list($attrib)
{
global $RCMAIL, $OUTPUT, $COMPOSE;
// add ID if not given
if (!$attrib['id'])
$attrib['id'] = 'rcmAttachmentList';
$out = "\n";
$jslist = array();
$button = '';
if ($attrib['icon_pos'] == 'left')
$COMPOSE['icon_pos'] = 'left';
if (is_array($COMPOSE['attachments'])) {
if ($attrib['deleteicon']) {
$button = html::img(array(
'src' => $RCMAIL->output->abs_url($attrib['deleteicon'], true),
'alt' => $RCMAIL->gettext('delete')
));
}
else if (rcube_utils::get_boolean($attrib['textbuttons'])) {
$button = rcube::Q($RCMAIL->gettext('delete'));
}
foreach ($COMPOSE['attachments'] as $id => $a_prop) {
if (empty($a_prop)) {
continue;
}
$link_content = sprintf('<span class="attachment-name" onmouseover="rcube_webmail.long_subject_title_ex(this)">%s</span> <span class="attachment-size">(%s)</span>',
rcube::Q($a_prop['name']), $RCMAIL->show_bytes($a_prop['size']));
$content_link = html::a(array(
'href' => "#load",
'class' => 'filename',
'onclick' => sprintf("return %s.command('load-attachment','rcmfile%s', this, event)", rcmail_output::JS_OBJECT_NAME, $id),
), $link_content);
$delete_link = html::a(array(
'href' => "#delete",
'title' => $RCMAIL->gettext('delete'),
'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this, event)", rcmail_output::JS_OBJECT_NAME, $id),
'class' => 'delete',
'tabindex' => $attrib['tabindex'] ?: '0',
'aria-label' => $RCMAIL->gettext('delete') . ' ' . $a_prop['name'],
), $button);
$out .= html::tag('li', array(
'id' => 'rcmfile'.$id,
'class' => rcube_utils::file2class($a_prop['mimetype'], $a_prop['name']),
),
$COMPOSE['icon_pos'] == 'left' ? $delete_link.$content_link : $content_link.$delete_link
);
$jslist['rcmfile'.$id] = array(
'name' => $a_prop['name'],
'complete' => true,
'mimetype' => $a_prop['mimetype']
);
}
}
if ($attrib['deleteicon'])
$COMPOSE['deleteicon'] = $RCMAIL->output->abs_url($attrib['deleteicon'], true);
else if (rcube_utils::get_boolean($attrib['textbuttons']))
$COMPOSE['textbuttons'] = true;
if ($attrib['cancelicon'])
$OUTPUT->set_env('cancelicon', $RCMAIL->output->abs_url($attrib['cancelicon'], true));
if ($attrib['loadingicon'])
$OUTPUT->set_env('loadingicon', $RCMAIL->output->abs_url($attrib['loadingicon'], true));
$OUTPUT->set_env('attachments', $jslist);
$OUTPUT->add_gui_object('attachmentlist', $attrib['id']);
// put tabindex value into data-tabindex attribute
if (isset($attrib['tabindex'])) {
$attrib['data-tabindex'] = $attrib['tabindex'];
unset($attrib['tabindex']);
}
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
/**
* Attachment upload form object for templates
*/
function rcmail_compose_attachment_form($attrib)
{
global $RCMAIL;
return $RCMAIL->upload_form($attrib, 'uploadform', 'send-attachment', array('multiple' => true));
}
/**
* Register a certain container as active area to drop files onto
*/
function rcmail_compose_file_drop_area($attrib)
{
global $OUTPUT;
if ($attrib['id']) {
$OUTPUT->add_gui_object('filedrop', $attrib['id']);
$OUTPUT->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
}
}
/**
* Editor mode selector object for templates
*/
function rcmail_editor_selector($attrib)
{
global $RCMAIL;
// determine whether HTML or plain text should be checked
$useHtml = rcmail_compose_editor_mode();
if (empty($attrib['editorid']))
$attrib['editorid'] = 'rcmComposeBody';
if (empty($attrib['name']))
$attrib['name'] = 'editorSelect';
$attrib['onchange'] = "return rcmail.command('toggle-editor', {id: '".$attrib['editorid']."', html: this.value == 'html'}, '', event)";
$select = new html_select($attrib);
$select->add(rcube::Q($RCMAIL->gettext('htmltoggle')), 'html');
$select->add(rcube::Q($RCMAIL->gettext('plaintoggle')), 'plain');
return $select->show($useHtml ? 'html' : 'plain');
}
/**
* Addressbooks list object for templates
*/
function rcmail_addressbook_list($attrib = array())
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmdirectorylist');
$out = '';
$line_templ = html::tag('li', array(
'id' => 'rcmli%s', 'class' => '%s'),
html::a(array('href' => '#list',
'rel' => '%s',
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list-addresses','%s',this)"), '%s'));
foreach ($RCMAIL->get_address_sources(false, true) as $j => $source) {
$id = strval(strlen($source['id']) ? $source['id'] : $j);
$js_id = rcube::JQ($id);
// set class name(s)
$class_name = 'addressbook';
if ($source['class_name'])
$class_name .= ' ' . $source['class_name'];
$out .= sprintf($line_templ,
rcube_utils::html_identifier($id,true),
$class_name,
$source['id'],
$js_id, ($source['name'] ?: $id));
}
$OUTPUT->add_gui_object('addressbookslist', $attrib['id']);
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
/**
* Contacts list object for templates
*/
function rcmail_contacts_list($attrib = array())
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmAddressList');
// set client env
$OUTPUT->add_gui_object('contactslist', $attrib['id']);
$OUTPUT->set_env('pagecount', 0);
$OUTPUT->set_env('current_page', 0);
$OUTPUT->include_script('list.js');
return $RCMAIL->table_output($attrib, array(), array('name'), 'ID');
}
/**
* Responses list object for templates
*/
function rcmail_compose_responses_list($attrib)
{
global $RCMAIL, $OUTPUT;
$attrib += array('id' => 'rcmresponseslist', 'tagname' => 'ul', 'cols' => 1);
$jsenv = array();
$list = new html_table($attrib);
foreach ($RCMAIL->get_compose_responses(true) as $response) {
$key = $response['key'];
$item = html::a(array(
'href' => '#' . urlencode($response['name']),
'class' => rtrim('insertresponse ' . $attrib['itemclass']),
'unselectable' => 'on',
'tabindex' => '0',
'rel' => $key,
), rcube::Q($response['name']));
$jsenv[$key] = $response;
$list->add(array(), $item);
}
// set client env
$OUTPUT->set_env('textresponses', $jsenv);
$OUTPUT->add_gui_object('responseslist', $attrib['id']);
return $list->show();
}
diff --git a/skins/classic/templates/compose.html b/skins/classic/templates/compose.html
index e14709799..d83e51c42 100644
--- a/skins/classic/templates/compose.html
+++ b/skins/classic/templates/compose.html
@@ -1,246 +1,246 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="productname" /> :: <roundcube:label name="compose" /></title>
<roundcube:include file="/includes/links.html" />
<roundcube:if condition="config:enable_spellcheck" />
<link rel="stylesheet" type="text/css" href="/googiespell.css" />
<roundcube:endif />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#compose-contacts { width: <roundcube:exp expression="!empty(cookie:composesplitterv1) ? cookie:composesplitterv1-5 : 195" />px; }
#compose-container { left: <roundcube:exp expression="!empty(cookie:composesplitterv1) ? cookie:composesplitterv1+5 : 205" />px; }
</style>
</head>
<roundcube:if condition="env:extwin" />
<body class="extwin">
<roundcube:object name="message" id="message" />
<roundcube:else />
<body>
<roundcube:include file="/includes/taskbar.html" />
<roundcube:include file="/includes/header.html" />
<roundcube:endif />
<div id="messagetoolbar">
<roundcube:if condition="env:extwin" />
<roundcube:button command="close" type="link" class="button back" classAct="button back" classSel="button backSel" title="close" content=" " />
<roundcube:else />
<roundcube:button command="list" type="link" class="button back" classAct="button back" classSel="button backSel" title="backtolist" content=" " />
<roundcube:endif />
<roundcube:button command="send" type="link" class="buttonPas send" classAct="button send" classSel="button sendSel" title="sendmessage" content=" " />
<roundcube:button name="addattachment" type="link" class="button attach" classAct="button attach" classSel="button attachSel" title="addattachment" onclick="rcmail_ui.show_popup('uploadmenu', true);return false" content=" " />
<roundcube:button command="insert-sig" type="link" class="buttonPas insertsig" classAct="button insertsig" classSel="button insertsigSel" title="insertsignature" content=" " />
<roundcube:button command="savedraft" type="link" class="buttonPas savedraft" classAct="button savedraft" classSel="button savedraftSel" title="savemessage" content=" " />
-<roundcube:if condition="config:enable_spellcheck" />
+<roundcube:if condition="!empty(env:spell_langs)" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="buttonPas spellcheck" classAct="button spellcheck" classSel="button spellcheckSel" title="checkspelling" content=" " />
<span id="spellmenulink" onclick="rcmail_ui.show_popup('spellmenu');return false"></span>
</span>
<roundcube:endif />
<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="rcmail_ui.show_popup('responsesmenu');return false"> </a>
<span class="dropbutton" style="display:none">
<roundcube:button command="compose-encrypted" type="link" class="buttonPas encrypt disabled" classAct="button encrypt" classSel="button encrypt" title="encryptmessagemailvelope" content=" " />
<span id="encryptionmenulink" onclick="rcmail_ui.show_popup('encryptionmenu');return false" style="display:none"></span>
</span>
<roundcube:container name="toolbar" id="compose-toolbar" />
<roundcube:button name="messageoptions" id="composemenulink" type="link" class="button messagemenu" title="messageoptions" onclick="rcmail_ui.show_popup('composemenu', true);return false" content=" " />
</div>
<roundcube:form name="form" method="post">
<div id="mainscreen">
<div id="compose-contacts">
<div class="boxtitle">
<roundcube:label name="contacts" />
<div id="abookcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<span style="float:left"> </span>
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
<div class="boxlistcontent">
<div class="searchbox">
<img id="searchmenulink" src="/images/icons/glass.png" width="16" height="16" />
<roundcube:object name="searchform" id="quicksearchbox" form="true" tabindex="13" />
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" width="13" height="13" />
</div>
<roundcube:object name="addressbooks" id="directorylist" />
<roundcube:object name="addresslist" id="contacts-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="abookactions">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="button disabled" classAct="button" content="To &raquo;" />
<roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="button disabled" classAct="button" content="Cc &raquo;" />
<roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="button disabled" classAct="button" content="Bcc &raquo;" />
<roundcube:container name="compose-contacts-toolbar" id="compose-contacts-toolbar" />
</div>
</div>
</div>
<script type="text/javascript">
var composesplitv1 = new rcube_splitter({id:'composesplitterv1', p1: 'compose-contacts', p2: 'compose-container', orientation: 'v', relative: true, start: 200});
rcmail.add_onload('composesplitv1.init()');
</script>
<div id="compose-container">
<div id="compose-headers-div" style="width: 100%;">
<table border="0" cellspacing="0" cellpadding="1" id="compose-headers">
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield formlinks">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('switch-task', 'settings/identities')"><roundcube:label name="editidents" /></a>
<roundcube:button command="extwin" image="/images/icons/extwin.png" width="15" height="15" title="openinextwin" id="openextwinlink" condition="!env:extwin" />
</td>
</tr><tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="2" tabindex="2" /></td>
</tr><tr id="compose-cc">
<td class="title top">
<a href="#cc" onclick="return rcmail_ui.hide_header_form('cc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_cc"><roundcube:label name="cc" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="2" tabindex="3" /></td>
</tr><tr id="compose-bcc">
<td class="title top">
<a href="#bcc" onclick="return rcmail_ui.hide_header_form('bcc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_bcc"><roundcube:label name="bcc" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
</tr><tr id="compose-replyto">
<td class="title top">
<a href="#replyto" onclick="return rcmail_ui.hide_header_form('replyto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_replyto"><roundcube:label name="replyto" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="5" /></td>
</tr><tr id="compose-followupto">
<td class="title top">
<a href="#followupto" onclick="return rcmail_ui.hide_header_form('followupto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_followupto"><roundcube:label name="followupto" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="7" /></td>
</tr><tr>
<td></td>
<td class="formlinks">
<a href="#cc" onclick="return rcmail_ui.show_header_form('cc')" id="cc-link"><roundcube:label name="addcc" /></a>
<span class="separator">|</span>
<a href="#bcc" onclick="return rcmail_ui.show_header_form('bcc')" id="bcc-link"><roundcube:label name="addbcc" /></a>
<span class="separator">|</span>
<a href="#reply-to" onclick="return rcmail_ui.show_header_form('replyto')" id="replyto-link"><roundcube:label name="addreplyto" /></a>
<span class="separator">|</span>
<a href="#followup-to" onclick="return rcmail_ui.show_header_form('followupto')" id="followupto-link"><roundcube:label name="addfollowupto" /></a>
</td>
</tr><tr>
<td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td>
<td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="8" /></td>
</tr>
</table>
</div>
<div id="compose-div">
<div id="compose-body-div">
<div id="compose-body-parent" class="boxlistcontent" style="overflow: hidden; top: 0">
<roundcube:object name="composeBody" id="compose-body" form="form" cols="70" rows="20" tabindex="9" />
</div>
<div class="boxfooter">
<div id="compose-buttons">
<roundcube:button type="input" command="send" class="button mainaction" label="sendmessage" tabindex="10" />
<roundcube:button type="input" command="list" class="button" label="cancel" tabindex="11" />
</div>
<div id="compose-editorfooter" class="pagenav">
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<span>
<label><roundcube:label name="editortype" /></label>
<roundcube:object name="editorSelector" editorid="compose-body" tabindex="12" />
</span>
<roundcube:endif />
</div>
</div>
</div>
<script type="text/javascript">
var composesplitv2 = new rcube_splitter({id:'composesplitterv2', p1: 'compose-body-div', p2: 'compose-attachments', orientation: 'v', relative: true, start: $('#compose-headers-div').width() - 175});
rcmail.add_onload('composesplitv2.init()');
</script>
<div id="compose-attachments">
<div class="boxtitle"><roundcube:label name="attachments" /></div>
<div class="boxlistcontent">
<roundcube:object name="composeAttachmentList" id="attachmentslist" loadingIcon="/images/display/loading_blue.gif" icon_pos="left" />
</div>
<div class="boxfooter">
<roundcube:button name="uploadmenulink" id="uploadmenulink" type="link" title="addattachment" class="button addgroup" onclick="rcmail_ui.show_popup('uploadmenu', true);return false" content=" " />
</div>
</div>
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div>
</div>
</div>
<div id="composeoptionsmenu" class="popupmenu">
<table>
<roundcube:if condition="!in_array('mdn_default', (array)config:dont_override)" />
<tr>
<td><label for="rcmcomposereceipt"><roundcube:label name="returnreceipt" />:</label></td>
<td><roundcube:object name="mdnCheckBox" form="form" id="rcmcomposereceipt" /></td>
</tr>
<roundcube:endif />
<roundcube:if condition="!in_array('dsn_default', (array)config:dont_override)" />
<tr>
<td><label for="rcmcomposedsn"><roundcube:label name="dsn" />:</label></td>
<td><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" /></td>
</tr>
<roundcube:endif />
<tr>
<td><label for="rcmcomposepriority"><roundcube:label name="priority" />:</label></td>
<td><roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></td>
</tr>
<roundcube:if condition="!config:no_save_sent_messages" />
<tr>
<td><label><roundcube:label name="savesentmessagein" />:</label></td>
<td><roundcube:object name="storetarget" maxlength="30" /></td>
</tr>
<roundcube:endif />
</table>
</div>
<div id="responsesmenu" class="popupmenu">
<ul id="textresponsesmenu">
<li><label class="comment"><roundcube:label name="insertresponse" /></label></li>
<roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" />
<li><label class="comment"><roundcube:label name="manageresponses" /></label></li>
<roundcube:button command="save-response" type="link-menuitem" label="newresponse" classAct="active" unselectable="on" />
<roundcube:button name="responses" type="link-menuitem" label="editresponses" class="active" onclick="return rcmail.command('switch-task', 'settings/responses')" />
</ul>
</div>
<div id="spellmenu" class="popupmenu selectable"></div>
</form>
<roundcube:object name="composeAttachmentForm" id="attachment-form" attachmentFieldSize="40" class="popupmenu" />
<div id="attachmentmenu" class="popupmenu">
<ul class="toolbarmenu">
<li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="openlink" classAct="openlink active" innerclass="openlink" /></li>
<li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="downloadlink" classAct="downloadlink active" innerclass="downloadlink" /></li>
<li><roundcube:button command="rename-attachment" id="attachmenurename" type="link" label="rename" class="renamelink" classAct="renamelink active" innerclass="renamelink" /></li>
<roundcube:container name="attachmentmenu" id="attachmentmenu" />
</ul>
</div>
<div id="encryptionmenu" class="popupmenu">
<ul>
<roundcube:button command="compose-encrypted" type="link-menuitem" label="encryptmessage" classAct="active" />
<roundcube:button command="compose-encrypted-signed" type="link-menuitem" label="encryptandsign" classAct="active" />
</ul>
</div>
<script type="text/javascript">
rcube_init_mail_ui();
</script>
</body>
</html>
diff --git a/skins/elastic/templates/compose.html b/skins/elastic/templates/compose.html
index 1859ef90b..054aba1bc 100644
--- a/skins/elastic/templates/compose.html
+++ b/skins/elastic/templates/compose.html
@@ -1,282 +1,282 @@
<roundcube:include file="includes/layout.html" />
<roundcube:include file="includes/menu.html" condition="!env:extwin && !env:framed" />
<roundcube:add_label name="recipientsadded" />
<roundcube:add_label name="nocontactselected" />
<roundcube:add_label name="recipient" />
<roundcube:add_label name="insert" />
<roundcube:add_label name="insertcontact" />
<roundcube:add_label name="recipientedit" />
<h1 class="voice"><roundcube:label name="compose" /></h1>
<!-- compose options and attachments list -->
<div class="sidebar listbox">
<div class="header">
<a class="button icon back-content-button" href="#content" data-hidden="big"><span class="inner"><roundcube:label name="back" /></span></a>
<span class="header-title all-sizes"><roundcube:label name="optionsandattachments" /></span>
</div>
<div class="scroller">
<!-- attachments -->
<div id="compose-attachments" class="file-upload" role="region" aria-labelledby="aria-label-compose-attachments">
<h2 id="aria-label-compose-attachments" class="voice"><roundcube:label name="attachments" /></h2>
<div class="upload-form">
<roundcube:object name="composeAttachmentForm" mode="hint" />
<button type="button" class="btn btn-secondary attach" tabindex="2" onclick="rcmail.upload_input('uploadform')"><roundcube:label name="addattachment" /></button>
</div>
<roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" tabindex="2" />
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div>
<!-- compose options -->
<div id="compose-options" class="formcontent" role="region" aria-labelledby="aria-label-composeoptions">
<h2 id="aria-label-composeoptions" class="voice"><roundcube:label name="arialabelcomposeoptions" /></h2>
<roundcube:container name="composeoptions" id="compose-options" />
<roundcube:if condition="!in_array('mdn_default', (array)config:dont_override)" />
<div class="form-group row form-check">
<label for="compose-mdn" class="col-form-label col-6"><roundcube:label name="returnreceipt" /></label>
<div class="col-6 form-check">
<roundcube:object name="mdnCheckBox" id="compose-mdn" noform="true" tabindex="2" class="form-check-input" />
</div>
</div>
<roundcube:endif />
<roundcube:if condition="!in_array('dsn_default', (array)config:dont_override)" />
<div class="form-group row form-check">
<label for="compose-dsn" class="col-form-label col-6"><roundcube:label name="dsn" /></label>
<div class="col-6 form-check">
<roundcube:object name="dsnCheckBox" id="compose-dsn" noform="true" tabindex="2" class="form-check-input" />
</div>
</div>
<roundcube:endif />
<div class="form-group row">
<label for="compose-priority" class="col-form-label col-6"><roundcube:label name="priority" /></label>
<div class="col-6">
<roundcube:object name="prioritySelector" id="compose-priority" noform="true" tabindex="2" />
</div>
</div>
<roundcube:if condition="!config:no_save_sent_messages" />
<div class="form-group row">
<label for="compose-store-target" class="col-form-label col-6"><roundcube:label name="savesentmessagein" /></label>
<div class="col-6">
<roundcube:object name="storetarget" id="compose-store-target" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<div class="form-group row hidden">
<label for="editor-selector" class="col-form-label col-6"><roundcube:label name="editortype" /></label>
<div class="col-6">
<roundcube:object name="editorSelector" id="editor-selector" editorid="composebody" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
</div>
</div>
</div>
<div class="content listbox selected" role="main">
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<div class="header">
<a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a>
<span class="header-title"><roundcube:label name="compose" /></span>
<!-- toolbar -->
<div id="messagetoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
<a class="button settings" href="#options" onclick="UI.show_sidebar()" data-hidden="big">
<span class="inner"><roundcube:label name="optionsandattachments"></span>
</a>
<roundcube:button command="savedraft" type="link" class="button save draft disabled" classAct="button save draft"
label="save" title="savemessage" tabindex="2" innerclass="inner" data-content-button="true" />
<span class="spacer"></span>
<roundcube:button name="addattachment" type="link" class="button attach"
label="attach" title="addattachment" data-hidden="small"
onclick="if (!$(this).is('.disabled')) rcmail.upload_input('uploadform')"
aria-haspopup="true" aria-expanded="false" tabindex="2" innerclass="inner" />
<roundcube:button command="insert-sig" type="link" class="button signature disabled" classAct="button signature"
label="signature" title="insertsignature" tabindex="2" innerclass="inner" />
<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" unselectable="on" tabindex="2" data-popup="responses-menu">
<span class="inner"><roundcube:label name="responses" /></span>
</a>
- <roundcube:if condition="config:enable_spellcheck" />
+ <roundcube:if condition="!empty(env:spell_langs)" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="button spellcheck disabled"
classAct="button spellcheck" classSel="button spellcheck pressed"
label="spellcheck" title="checkspelling" tabindex="2" innerclass="inner" />
<a href="#languages" class="button dropdown" tabindex="2" data-popup="spell-menu">
<span class="inner"><roundcube:label name="language" /></span>
</a>
</span>
<roundcube:endif />
<span class="dropbutton" style="display:none">
<roundcube:button command="compose-encrypted" type="link" class="button encrypt disabled"
classAct="button encrypt" classSel="button encrypt selected" innerclass="inner"
label="encrypt" title="encryptmessagemailvelope" tabindex="2" />
<a href="#encryption" id="encryption-menu-button" class="button dropdown" tabindex="2" data-popup="encryption-menu">
<span class="inner"><roundcube:label name="encryptmessagemailvelope" /></span>
</a>
</span>
<roundcube:container name="toolbar" id="compose-toolbar" />
</div>
</div>
<div id="compose-content" class="formcontainer content scroller">
<roundcube:object name="composeFormHead" role="main" class="formcontent" />
<!-- message headers -->
<div id="compose-headers" role="region" aria-labelledby="aria-label-composeheaders">
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
<div class="compose-headers">
<div id="compose_from" class="form-group row">
<label for="_from" class="col-2 col-form-label"><roundcube:label name="from" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="from" id="_from" form="form" tabindex="1" class="form-control" />
<span class="input-group-append">
<a href="#identities" onclick="return rcmail.command('switch-task', 'settings/identities')" class="input-group-text icon edit" title="<roundcube:label name="editidents" />" tabindex="1"><span class="inner"><roundcube:label name="editidents" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_to" class="form-group row">
<label for="_to" class="col-2 col-form-label"><roundcube:label name="to" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="to" id="_to" form="form" tabindex="1" aria-required="true" data-recipient-input="true" />
<span class="input-group-append">
<a href="#add-contact" onclick="UI.recipient_selector('to')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
</span>
<span class="input-group-append">
<a href="#add-header" data-popup="headers-menu" class="input-group-text icon add" title="<roundcube:label name="addheader" />" tabindex="1"><span class="inner"><roundcube:label name="addheader" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_cc" class="hidden form-group row">
<label for="_cc" class="col-2 col-form-label"><roundcube:label name="cc" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="cc" id="_cc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#add-contact" onclick="UI.recipient_selector('cc')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
</span>
<span class="input-group-append">
<a href="#delete" onclick="UI.header_reset('_cc')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_bcc" class="hidden form-group row">
<label for="_bcc" class="col-2 col-form-label"><roundcube:label name="bcc" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="bcc" id="_bcc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#add-contact" onclick="UI.recipient_selector('bcc')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
</span>
<span class="input-group-append">
<a href="#delete" onclick="UI.header_reset('_bcc')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_replyto" class="hidden form-group row">
<label for="_replyto" class="col-2 col-form-label"><roundcube:label name="replyto" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="replyto" id="_replyto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#add-contact" onclick="UI.recipient_selector('replyto')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
</span>
<span class="input-group-append">
<a href="#delete" onclick="UI.header_reset('_replyto')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_followupto" class="hidden form-group row">
<label for="_followupto" class="col-2 col-form-label"><roundcube:label name="followupto" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="followupto" id="_followupto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#add-contact" onclick="UI.recipient_selector('followupto')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
</span>
<span class="input-group-append">
<a href="#delete" onclick="UI.header_reset('_followupto')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_subject" class="form-group row">
<label for="compose-subject" class="col-2 col-form-label"><roundcube:label name="subject" /></label>
<div class="col-10">
<roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="1" class="form-control" />
</div>
</div>
</div>
</div>
<!-- message compose body -->
<div id="composebodycontainer">
<label for="composebody" class="voice"><roundcube:label name="arialabelmessagebody" /></label>
<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" class="form-control" tabindex="1" />
<div id="composestatusbar"></div>
</div>
</form>
<div class="formbuttons">
<roundcube:button command="send" class="btn btn-primary send" label="send" tabindex="1" data-content-button="true" />
</div>
</div>
</div>
<roundcube:object name="composeAttachmentForm" id="uploadform" mode="smart" />
<div id="spell-menu" class="popupmenu" data-popup-init="spellmenu"></div>
<div id="headers-menu" class="popupmenu" data-popup-init="headersmenu">
<h3 id="aria-label-headersmenu" class="voice"><roundcube:label name="arialabelheadersmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-headersmenu">
<li role="menuitem"><a data-target="cc" href="#" role="button" tabindex="-1" class="recipient active"><roundcube:label name="cc" /></a></li>
<li role="menuitem"><a data-target="bcc" href="#" role="button" tabindex="-1" class="recipient active"><roundcube:label name="bcc" /></a></li>
<li role="menuitem"><a data-target="replyto" href="#" role="button" tabindex="-1" class="recipient active"><roundcube:label name="replyto" /></a></li>
<li role="menuitem"><a data-target="followupto" href="#" role="button" tabindex="-1" class="recipient active"><roundcube:label name="followupto" /></a></li>
</ul>
</div>
<div id="responses-menu" class="popupmenu">
<h3 id="aria-label-responsesmenu" class="voice"><roundcube:label name="arialabelresponsesmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-responsesmenu">
<li role="separator" class="separator"><label><roundcube:label name="insertresponse" /></label></li>
<roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" />
<li role="separator" class="separator"><label><roundcube:label name="manageresponses" /></label></li>
<roundcube:button command="save-response" type="link-menuitem" label="newresponse" class="create responses" classAct="create responses active" unselectable="on" />
<roundcube:button name="responses" type="link-menuitem" label="editresponses" class="edit responses active" onclick="return rcmail.command('switch-task', 'settings/responses')" />
</ul>
</div>
<div id="attachmentmenu" class="popupmenu">
<h3 id="aria-label-attachmentmenu" class="voice"><roundcube:label name="arialabelattachmentmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-attachmentmenu">
<roundcube:button command="open-attachment" id="attachmenuopen" type="link-menuitem" label="open" class="extwin" classAct="extwin active" />
<roundcube:button command="download-attachment" id="attachmenudownload" type="link-menuitem" label="download" class="download" classAct="download active" />
<roundcube:button command="rename-attachment" id="attachmenurename" type="link-menuitem" label="rename" class="rename" classAct="rename active" />
<roundcube:container name="attachmentmenu" id="attachmentoptionsmenu" />
</ul>
</div>
<div id="encryption-menu" class="popupmenu">
<ul class="toolbarmenu listing" role="menu">
<roundcube:button command="compose-encrypted" type="link-menuitem" label="encryptmessage" class="encrypt" classAct="encrypt active" />
<roundcube:button command="compose-encrypted-signed" type="link-menuitem" label="encryptandsign" class="encrypt sign" classAct="encrypt sign active" />
</ul>
</div>
<div id="recipient-dialog" class="popupmenu" role="region" aria-labelledby="aria-label-composecontacts">
<div class="listbox">
<roundcube:object name="searchform" id="searchform" wrapper="searchbar toolbar"
label="contactsearchform" buttontitle="findcontacts" ariatag="h2" class="no-bs" />
<div class="scroller" tabindex="-1">
<roundcube:object name="addressbooks" id="directorylist" class="treelist listing iconized"
summary="ariasummarycomposecontacts" />
<roundcube:object name="addresslist" id="contacts-table" class="listing iconized contactlist"
noheader="true" role="listbox" data-list="contact_list" data-list-select-replace="#recipient-dialog .pagenav-text" />
</div>
<roundcube:include file="includes/pagenav.html" />
</div>
</div>
<roundcube:include file="includes/footer.html" />
diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html
index df86b6ef5..a86124ac5 100644
--- a/skins/larry/templates/compose.html
+++ b/skins/larry/templates/compose.html
@@ -1,239 +1,239 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<roundcube:if condition="config:enable_spellcheck" />
<link rel="stylesheet" type="text/css" href="/googiespell.css" />
<roundcube:endif />
</head>
<roundcube:if condition="env:extwin" /><body class="extwin"><roundcube:else /><body><roundcube:endif />
<roundcube:include file="/includes/header.html" />
<div id="mainscreen">
<h1 class="voice"><roundcube:object name="pagetitle" /></h1>
<!-- toolbar -->
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" label="cancel" condition="!env:extwin" tabindex="2" />
<roundcube:button command="close" type="link" class="button close disabled" classAct="button close" label="cancel" condition="env:extwin" tabindex="2" />
<span class="spacer"></span>
<roundcube:button command="send" type="link" class="button send disabled" classAct="button send" label="send" title="sendmessage" tabindex="2" />
<roundcube:button command="savedraft" type="link" class="button savedraft disabled" classAct="button savedraft" label="save" title="savemessage" tabindex="2" />
<span class="spacer"></span>
- <roundcube:if condition="config:enable_spellcheck" />
+ <roundcube:if condition="!empty(env:spell_langs)" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="button spellcheck disabled" classAct="button spellcheck" classSel="button spellcheck pressed" label="spellcheck" title="checkspelling" tabindex="2" />
<a href="#languages" class="dropbuttontip" id="spellmenulink" onclick="UI.toggle_popup('spellmenu',event);return false" aria-haspopup="true" aria-expanded="false" tabindex="2">Select Spell Language</a>
</span>
<roundcube:endif />
<roundcube:button name="addattachment" type="link" class="button attach" label="attach" title="addattachment" onclick="rcmail.upload_input('uploadform')" aria-haspopup="true" aria-expanded="false" tabindex="2" />
<roundcube:button command="insert-sig" type="link" class="button insertsig disabled" classAct="button insertsig" label="signature" title="insertsignature" tabindex="2" />
<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="UI.toggle_popup('responsesmenu',event);return false" tabindex="2" aria-haspopup="true" aria-expanded="false" aria-owns="textresponsesmenu"><roundcube:label name="responses" /></a>
<span class="dropbutton" style="display:none">
<roundcube:button command="compose-encrypted" type="link" class="button encrypt disabled" classAct="button encrypt" classSel="button encrypt selected" label="encrypt" title="encryptmessagemailvelope" tabindex="2" />
<a href="#compose-encrypted" class="dropbuttontip" id="encryptionmenulink" onclick="UI.toggle_popup('encryptionmenu',event);return false" aria-haspopup="true" aria-expanded="false" tabindex="2" style="display:none"></a>
</span>
<roundcube:container name="toolbar" id="compose-toolbar" />
</div>
<div id="mainscreencontent">
<div id="composeview-left">
<!-- inline address book -->
<div id="compose-contacts" class="uibox listbox" role="region" aria-labelledby="aria-label-composecontacts">
<h2 id="aria-label-composecontacts" class="boxtitle"><roundcube:label name="contacts" /></h2>
<div class="listsearchbox" role="search" aria-labelledby="aria-label-composequicksearch">
<h3 id="aria-label-composequicksearch" class="voice"><roundcube:label name="arialabelcontactquicksearch" /></h3>
<div class="searchbox">
<label for="contactsearchbox" class="voice"><roundcube:label name="arialabelcontactsearchbox" /></label>
<roundcube:object name="searchform" id="contactsearchbox" />
<a id="searchmenulink" class="iconbutton searchicon"> </a>
<roundcube:button command="reset-search" type="link" class="iconbutton reset" title="resetsearch" content=" " />
</div>
</div>
<roundcube:object name="addressbooks" id="directorylist" class="treelist listing" summary="ariasummarycomposecontacts" />
<div class="scroller withfooter" tabindex="-1">
<roundcube:object name="addresslist" id="contacts-table" class="listing iconized" noheader="true" role="listbox" />
</div>
<div class="boxfooter">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="listbutton addto disabled" classAct="listbutton addto" innerClass="inner" content="To+" /><roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="listbutton addcc disabled" classAct="listbutton addcc" innerClass="inner" content="Cc+" /><roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="listbutton addbcc disabled" classAct="listbutton addbcc" innerClass="inner" content="Bcc+" /><roundcube:container name="compose-contacts-toolbar" id="compose-contacts-toolbar" />
</div>
<div class="boxpagenav">
<roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" label="first" />
<roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" label="previous" />
<roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" label="next" />
<roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" label="last" />
</div>
</div>
</div>
<div id="composeview-right" role="main">
<roundcube:form name="form" method="post" id="compose-content" class="uibox">
<!-- message headers -->
<div id="composeheaders" role="region" aria-labelledby="aria-label-composeheaders">
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
<a href="#options" id="composeoptionstoggle" class="moreheaderstoggle" title="<roundcube:label name='togglecomposeoptions' />" aria-expanded="false"><span class="iconlink"></span></a>
<table class="headers-table compose-headers">
<tbody>
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('switch-task', 'settings/identities')" class="iconlink edit" tabindex="0"><roundcube:label name="editidents" /></a>
</td>
</tr><tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" aria-required="true" /></td>
</tr><tr id="compose-cc">
<td class="title top">
<label for="_cc"><roundcube:label name="cc" /></label>
<a href="#cc" onclick="return UI.hide_header_row('cc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="cc" /></a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td>
</tr><tr id="compose-bcc">
<td class="title top">
<label for="_bcc"><roundcube:label name="bcc" /></label>
<a href="#bcc" onclick="return UI.hide_header_row('bcc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="bcc" /></a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td>
</tr><tr id="compose-replyto">
<td class="title top">
<label for="_replyto"><roundcube:label name="replyto" /></label>
<a href="#replyto" onclick="return UI.hide_header_row('replyto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="replyto" /></a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="1" /></td>
</tr><tr id="compose-followupto">
<td class="title top">
<label for="_followupto"><roundcube:label name="followupto" /></label>
<a href="#followupto" onclick="return UI.hide_header_row('followupto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="followupto" /></a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="1" /></td>
</tr><tr>
<td></td>
<td class="formlinks">
<a href="#cc" onclick="return UI.show_header_row('cc')" id="cc-link" class="iconlink add" tabindex="3"><roundcube:label name="addcc" /></a>
<a href="#bcc" onclick="return UI.show_header_row('bcc')" id="bcc-link" class="iconlink add" tabindex="3"><roundcube:label name="addbcc" /></a>
<a href="#reply-to" onclick="return UI.show_header_row('replyto')" id="replyto-link" class="iconlink add" tabindex="3"><roundcube:label name="addreplyto" /></a>
<a href="#followup-to" onclick="return UI.show_header_row('followupto')" id="followupto-link" class="iconlink add" tabindex="3"><roundcube:label name="addfollowupto" /></a>
</td>
</tr><tr>
<td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td>
<td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="1" /></td>
</tr>
</tbody>
</table>
<div id="composebuttons" class="formbuttons">
<roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" label="openinextwin" condition="!env:extwin" />
</div>
<!-- (collapsible) message options -->
<div id="composeoptions" role="region" aria-labelledby="aria-label-composeoptions">
<h2 id="aria-label-composeoptions" class="voice"><roundcube:label name="arialabelcomposeoptions" /></h2>
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<span class="composeoption">
<label><roundcube:label name="editortype" />
<roundcube:object name="editorSelector" editorid="composebody" tabindex="4" /></label>
</span>
<roundcube:endif />
<span class="composeoption">
<label for="rcmcomposepriority"><roundcube:label name="priority" />
<roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" tabindex="4" /></label>
</span>
<roundcube:if condition="!in_array('mdn_default', (array)config:dont_override)" />
<span class="composeoption">
<label><roundcube:object name="mdnCheckBox" form="form" id="rcmcomposereceipt" tabindex="4" /> <roundcube:label name="returnreceipt" /></label>
</span>
<roundcube:endif />
<roundcube:if condition="!in_array('dsn_default', (array)config:dont_override)" />
<span class="composeoption">
<label><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" tabindex="4" /> <roundcube:label name="dsn" /></label>
</span>
<roundcube:endif />
<roundcube:if condition="!config:no_save_sent_messages" />
<span class="composeoption">
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="4" /></label>
</span>
<roundcube:endif />
<roundcube:container name="composeoptions" id="composeoptions" />
</div>
</div>
<!-- message compose body -->
<div id="composeview-bottom">
<div id="composebodycontainer">
<label for="composebody" class="voice"><roundcube:label name="arialabelmessagebody" /></label>
<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="1" />
</div>
<div id="compose-attachments" class="rightcol" role="region" aria-labelledby="aria-label-composeattachments">
<h2 id="aria-label-composeattachments" class="voice"><roundcube:label name="attachments" /></h2>
<div class="upload-form" style="text-align:center; margin-bottom:10px">
<roundcube:object name="composeAttachmentForm" mode="hint" />
<a class="button" tabindex="1" href="#" onclick="rcmail.upload_input('uploadform')"><roundcube:label name="addattachment" /></a>
</div>
<roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" tabindex="1" />
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div>
<!--
<div id="composeformbuttons" class="footerleft formbuttons floating">
<roundcube:button type="input" command="send" class="button mainaction" label="sendmessage" tabindex="11" />
<roundcube:button type="input" command="savedraft" class="button" label="savemessage" tabindex="12" />
<roundcube:button type="input" command="list" class="button" label="cancel" tabindex="13" />
</div>
-->
</div>
</form>
</div><!-- end mailview-right -->
</div><!-- end mainscreencontent -->
</div><!-- end mainscreen -->
<roundcube:object name="composeAttachmentForm" id="uploadform" mode="smart" />
<div id="spellmenu" class="popupmenu" aria-hidden="true"></div>
<div id="responsesmenu" class="popupmenu" aria-hidden="true">
<h3 id="aria-label-responsesmenu" class="voice"><roundcube:label name="arialabelresponsesmenu" /></h3>
<ul class="toolbarmenu" id="textresponsesmenu" role="menu" aria-labelledby="aria-label-responsesmenu">
<li role="separator" class="separator" id=""><label><roundcube:label name="insertresponse" /></label></li>
<roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" />
<li role="separator" class="separator"><label><roundcube:label name="manageresponses" /></label></li>
<roundcube:button command="save-response" type="link-menuitem" label="newresponse" classAct="active" unselectable="on" />
<roundcube:button name="responses" type="link-menuitem" label="editresponses" class="active" onclick="return rcmail.command('switch-task', 'settings/responses')" />
</ul>
</div>
<div id="attachmentmenu" class="popupmenu" aria-hidden="true">
<ul class="toolbarmenu" id="attachmentoptionsmenu" role="menu">
<roundcube:button command="open-attachment" id="attachmenuopen" type="link-menuitem" label="open" class="icon" classAct="icon active" innerclass="icon extwin" />
<roundcube:button command="download-attachment" id="attachmenudownload" type="link-menuitem" label="download" class="icon" classAct="icon active" innerclass="icon download" />
<roundcube:button command="rename-attachment" id="attachmenurename" type="link-menuitem" label="rename" class="icon" classAct="icon active" innerclass="icon rename" />
<roundcube:container name="attachmentmenu" id="attachmentoptionsmenu" />
</ul>
</div>
<div id="encryptionmenu" class="popupmenu" aria-hidden="true">
<ul class="toolbarmenu" role="menu">
<roundcube:button command="compose-encrypted" type="link-menuitem" label="encryptmessage" classAct="active" />
<roundcube:button command="compose-encrypted-signed" type="link-menuitem" label="encryptandsign" classAct="active" />
</ul>
</div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Aug 25, 10:19 PM (16 m, 17 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
257824
Default Alt Text
(121 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment