Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2518267
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
125 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/program/actions/mail/compose.php b/program/actions/mail/compose.php
index 33085a95a..6599e6a2a 100644
--- a/program/actions/mail/compose.php
+++ b/program/actions/mail/compose.php
@@ -1,1652 +1,1653 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_compose extends rcmail_action_mail_index
{
protected static $COMPOSE_ID;
protected static $COMPOSE;
protected static $MESSAGE;
protected static $MESSAGE_BODY;
protected static $CID_MAP = [];
protected static $HTML_MODE = false;
protected static $SENDMAIL;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
self::$COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
self::$COMPOSE = null;
if (self::$COMPOSE_ID && $_SESSION['compose_data_' . self::$COMPOSE_ID]) {
self::$COMPOSE =& $_SESSION['compose_data_' . self::$COMPOSE_ID];
}
// give replicated session storage some time to synchronize
$retries = 0;
while (self::$COMPOSE_ID && !is_array(self::$COMPOSE) && $rcmail->db->is_replicated() && $retries++ < 5) {
usleep(500000);
$rcmail->session->reload();
if ($_SESSION['compose_data_' . self::$COMPOSE_ID]) {
self::$COMPOSE =& $_SESSION['compose_data_' . self::$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(self::$COMPOSE)) {
// Infinite redirect prevention in case of broken session (#1487028)
if (self::$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 (
!isset($_SESSION['last_compose_session'])
|| self::$COMPOSE_ID != $_SESSION['last_compose_session']
) {
rcube::raise_error(['code' => 450], false, true);
}
}
self::$COMPOSE_ID = uniqid(mt_rand());
$params = rcube_utils::request2param(rcube_utils::INPUT_GET, 'task|action', true);
$_SESSION['compose_data_' . self::$COMPOSE_ID] = [
'id' => self::$COMPOSE_ID,
'param' => $params,
'mailbox' => isset($params['mbox']) && strlen($params['mbox'])
? $params['mbox'] : $rcmail->storage->get_folder(),
];
self::$COMPOSE =& $_SESSION['compose_data_' . self::$COMPOSE_ID];
self::process_compose_params(self::$COMPOSE);
// check if folder for saving sent messages exists and is subscribed (#1486802)
if (!empty(self::$COMPOSE['param']['sent_mbox'])) {
$sent_folder = self::$COMPOSE['param']['sent_mbox'];
rcmail_sendmail::check_sent_folder($sent_folder, true);
}
// redirect to a unique URL with all parameters stored in session
$rcmail->output->redirect([
'_action' => 'compose',
'_id' => self::$COMPOSE['id'],
'_search' => !empty($_REQUEST['_search']) ? $_REQUEST['search'] : null,
]);
}
// add some labels to client
$rcmail->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'
);
$rcmail->output->set_pagetitle($rcmail->gettext('compose'));
$rcmail->output->set_env('compose_id', self::$COMPOSE['id']);
$rcmail->output->set_env('session_id', session_id());
$rcmail->output->set_env('mailbox', $rcmail->storage->get_folder());
$rcmail->output->set_env('top_posting', intval($rcmail->config->get('reply_mode')) > 0);
$rcmail->output->set_env('sig_below', $rcmail->config->get('sig_below'));
$rcmail->output->set_env('save_localstorage', (bool) $rcmail->config->get('compose_save_localstorage'));
$rcmail->output->set_env('is_sent', false);
$rcmail->output->set_env('mimetypes', self::supported_mimetypes());
$rcmail->output->set_env('keyservers', $rcmail->config->keyservers());
$rcmail->output->set_env('mailvelope_main_keyring', $rcmail->config->get('mailvelope_main_keyring'));
$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)) {
$rcmail->output->set_env('drafts_mailbox', $drafts_mbox);
$rcmail->output->set_env('draft_autosave', $rcmail->config->get('draft_autosave'));
}
// default font for HTML editor
$font = self::font_defs($rcmail->config->get('default_font'));
if ($font && !is_array($font)) {
$rcmail->output->set_env('default_font', $font);
}
// default font size for HTML editor
if ($font_size = $rcmail->config->get('default_font_size')) {
$rcmail->output->set_env('default_font_size', $font_size);
}
$compose_mode = null;
$msg_uid = null;
// get reference message and set compose mode
if (!empty(self::$COMPOSE['param']['draft_uid'])) {
$msg_uid = self::$COMPOSE['param']['draft_uid'];
$compose_mode = rcmail_sendmail::MODE_DRAFT;
$rcmail->output->set_env('draft_id', $msg_uid);
$rcmail->storage->set_folder($drafts_mbox);
}
else if (!empty(self::$COMPOSE['param']['reply_uid'])) {
$msg_uid = self::$COMPOSE['param']['reply_uid'];
$compose_mode = rcmail_sendmail::MODE_REPLY;
}
else if (!empty(self::$COMPOSE['param']['forward_uid'])) {
$msg_uid = self::$COMPOSE['param']['forward_uid'];
$compose_mode = rcmail_sendmail::MODE_FORWARD;
self::$COMPOSE['forward_uid'] = $msg_uid;
self::$COMPOSE['as_attachment'] = !empty(self::$COMPOSE['param']['attachment']);
}
else if (!empty(self::$COMPOSE['param']['uid'])) {
$msg_uid = self::$COMPOSE['param']['uid'];
$compose_mode = rcmail_sendmail::MODE_EDIT;
}
self::$COMPOSE['mode'] = $compose_mode;
if ($compose_mode) {
$rcmail->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) {
$rcmail->output->set_env('show_sig_later', true);
}
}
else if ($config_show_sig == 1) {
$rcmail->output->set_env('show_sig', true);
}
else if ($config_show_sig == 2 && empty($compose_mode)) {
$rcmail->output->set_env('show_sig', true);
}
else if (
$config_show_sig == 3
&& ($compose_mode == rcmail_sendmail::MODE_REPLY || $compose_mode == rcmail_sendmail::MODE_FORWARD)
) {
$rcmail->output->set_env('show_sig', true);
}
if (!empty($msg_uid) && (empty(self::$COMPOSE['as_attachment']) || $compose_mode == rcmail_sendmail::MODE_DRAFT)) {
$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);
}
self::$MESSAGE = new rcube_message($msg_uid);
// make sure message is marked as read
if (self::$MESSAGE->headers && self::$MESSAGE->context === null && empty(self::$MESSAGE->headers->flags['SEEN'])) {
$rcmail->storage->set_flag($msg_uid, 'SEEN');
}
if (!empty(self::$MESSAGE->headers->charset)) {
$rcmail->storage->set_charset(self::$MESSAGE->headers->charset);
}
if (!self::$MESSAGE->headers) {
// error
}
else if ($compose_mode == rcmail_sendmail::MODE_FORWARD || $compose_mode == rcmail_sendmail::MODE_REPLY) {
if ($compose_mode == rcmail_sendmail::MODE_REPLY) {
self::$COMPOSE['reply_uid'] = self::$MESSAGE->context === null ? $msg_uid : null;
if (!empty(self::$COMPOSE['param']['all'])) {
self::$MESSAGE->reply_all = self::$COMPOSE['param']['all'];
}
}
else {
self::$COMPOSE['forward_uid'] = $msg_uid;
}
self::$COMPOSE['reply_msgid'] = self::$MESSAGE->headers->messageID;
self::$COMPOSE['references'] = trim(self::$MESSAGE->headers->references . " " . self::$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 = self::$COMPOSE['mailbox'])
&& rcmail_sendmail::check_sent_folder($sent_folder, false)
) {
self::$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 = self::$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') {
self::$COMPOSE['reply_uid'] = $info['uid'];
}
else if ($info['type'] == 'forward') {
self::$COMPOSE['forward_uid'] = $info['uid'];
}
self::$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)
) {
self::$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
if (($msgid = self::$MESSAGE->headers->get('message-id')) && !preg_match('/^mid:[0-9]+$/', $msgid)) {
self::$COMPOSE['param']['message-id'] = $msgid;
}
// use message UID as draft_id
$rcmail->output->set_env('draft_id', $msg_uid);
}
if ($in_reply_to = self::$MESSAGE->headers->get('in-reply-to')) {
self::$COMPOSE['reply_msgid'] = '<' . $in_reply_to . '>';
}
self::$COMPOSE['references'] = self::$MESSAGE->headers->references;
}
}
else {
self::$MESSAGE = new stdClass();
// apply mailto: URL parameters
if (!empty(self::$COMPOSE['param']['in-reply-to'])) {
self::$COMPOSE['reply_msgid'] = '<' . trim(self::$COMPOSE['param']['in-reply-to'], '<> ') . '>';
}
if (!empty(self::$COMPOSE['param']['references'])) {
self::$COMPOSE['references'] = self::$COMPOSE['param']['references'];
}
}
if (!empty(self::$COMPOSE['reply_msgid'])) {
$rcmail->output->set_env('reply_msgid', self::$COMPOSE['reply_msgid']);
}
// Initialize helper class to build the UI
self::$SENDMAIL = new rcmail_sendmail(self::$COMPOSE, ['message' => self::$MESSAGE]);
// process self::$MESSAGE body/attachments, set self::$MESSAGE_BODY/$HTML_MODE vars and some session data
self::$MESSAGE_BODY = self::prepare_message_body();
// register UI objects (Note: some objects are registered by rcmail_sendmail above)
$rcmail->output->add_handlers([
'composebody' => [$this, 'compose_body'],
'composeattachmentlist' => [$this, 'compose_attachment_list'],
'composeattachmentform' => [$this, 'compose_attachment_form'],
'composeattachment' => [$this, 'compose_attachment_field'],
'filedroparea' => [$this, 'compose_file_drop_area'],
'editorselector' => [$this, 'editor_selector'],
'addressbooks' => [$this, 'addressbook_list'],
'addresslist' => [$this, 'contacts_list'],
'responseslist' => [$this, 'compose_responses_list'],
]);
$rcmail->output->include_script('publickey.js');
self::spellchecker_init();
$rcmail->output->send('compose');
}
// process compose request parameters
public static function process_compose_params(&$COMPOSE)
{
if (!empty($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 = ['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 (
!empty($COMPOSE['param']['forward_uid'])
&& $COMPOSE['param']['forward_uid'] == '*'
&& !empty($_SESSION['search'][1])
&& 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 = ['safe' => false, 'inline_html' => true];
$COMPOSE['param']['body'] = self::prepare_html_body($COMPOSE['param']['body'], $wash_params);
}
}
$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 (!empty($plugin['attachments'])) {
foreach ($plugin['attachments'] as $attach) {
// we have structured data
if (is_array($attach)) {
$attachment = $attach + ['group' => self::$COMPOSE_ID];
}
// only a file path is given
else {
$filename = basename($attach);
$attachment = [
'group' => self::$COMPOSE_ID,
'name' => $filename,
'mimetype' => rcube_mime::file_content_type($attach, $filename),
'size' => filesize($attach),
'path' => $attach,
];
}
// save attachment if valid
if (
(!empty($attachment['data']) && !empty($attachment['name']))
|| (!empty($attachment['path']) && file_exists($attachment['path']))
) {
$attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment);
}
if (!empty($attachment['status']) && empty($attachment['abort'])) {
unset($attachment['data'], $attachment['status'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
}
}
}
public static function compose_editor_mode()
{
static $useHtml;
if ($useHtml !== null) {
return $useHtml;
}
$rcmail = rcmail::get_instance();
$html_editor = intval($rcmail->config->get('htmleditor'));
$compose_mode = self::$COMPOSE['mode'];
if (isset(self::$COMPOSE['param']['html']) && is_bool(self::$COMPOSE['param']['html'])) {
$useHtml = self::$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 = self::message_is_html();
}
else if ($compose_mode == rcmail_sendmail::MODE_REPLY) {
$useHtml = $html_editor == 1 || ($html_editor >= 2 && self::message_is_html());
}
else if ($compose_mode == rcmail_sendmail::MODE_FORWARD) {
$useHtml = $html_editor == 1 || $html_editor == 4
|| ($html_editor == 3 && self::message_is_html());
}
else {
$useHtml = $html_editor == 1 || $html_editor == 4;
}
return $useHtml;
}
public static function message_is_html()
{
return rcmail::get_instance()->config->get('prefer_html')
&& (self::$MESSAGE instanceof rcube_message)
&& self::$MESSAGE->has_html_part(true);
}
public static function spellchecker_init()
{
$rcmail = rcmail::get_instance();
// Set language list
if ($rcmail->config->get('enable_spellcheck')) {
$spellchecker = new rcube_spellchecker();
}
else {
return;
}
$spellcheck_langs = $spellchecker->languages();
if (empty($spellcheck_langs)) {
if ($err = $spellchecker->error()) {
rcube::raise_error(['code' => 500,
'file' => __FILE__, 'line' => __LINE__,
'message' => "Spell check engine error: " . trim($err)],
true, false
);
}
}
else {
$dictionary = (bool) $rcmail->config->get('spellcheck_dictionary');
$lang = $_SESSION['language'];
// if not found in the list, try with two-letter code
if (empty($spellcheck_langs[$lang])) {
$lang = strtolower(substr($lang, 0, 2));
}
if (empty($spellcheck_langs[$lang])) {
$lang = 'en';
}
// include GoogieSpell
$rcmail->output->include_script('googiespell.js');
$rcmail->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(['_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');
$rcmail->output->add_label('checking');
$rcmail->output->set_env('spell_langs', $spellcheck_langs);
$rcmail->output->set_env('spell_lang', $lang);
}
}
public static function prepare_message_body()
{
$rcmail = rcmail::get_instance();
$body = '';
// 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 (!empty(self::$COMPOSE['param']['body'])) {
$body = self::$COMPOSE['param']['body'];
$isHtml = !empty(self::$COMPOSE['param']['html']);
}
// forward as attachment
else if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD && !empty(self::$COMPOSE['as_attachment'])) {
$isHtml = self::compose_editor_mode();
self::write_forward_attachments();
}
// reply/edit/draft/forward
else if (!empty(self::$COMPOSE['mode'])
&& (self::$COMPOSE['mode'] != rcmail_sendmail::MODE_REPLY || intval($rcmail->config->get('reply_mode')) != -1)
) {
$isHtml = self::compose_editor_mode();
$messages = [];
// Create a (fake) image attachments map. We need it before we handle
// the message body. After that we'll go throughout the list and check
// which images were used in the body and attach them for real or skip.
if ($isHtml) {
self::$CID_MAP = self::cid_map(self::$MESSAGE);
}
// set is_safe flag (before HTML body washing)
if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT) {
self::$MESSAGE->is_safe = true;
}
else {
self::check_safe(self::$MESSAGE);
}
if (!empty(self::$MESSAGE->parts)) {
// collect IDs of message/rfc822 parts
foreach (self::$MESSAGE->mime_parts() as $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
}
foreach (self::$MESSAGE->parts as $part) {
if (!empty($part->realtype) && $part->realtype == 'multipart/encrypted') {
// find the encrypted message payload part
if ($pgp_mime_part = self::$MESSAGE->get_multipart_encrypted_part()) {
$rcmail->output->set_env('pgp_mime_message', [
'_mbox' => $rcmail->storage->get_folder(),
'_uid' => self::$MESSAGE->uid,
'_part' => $pgp_mime_part->mime_id,
]);
}
continue;
}
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || self::$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 = self::compose_part_body($part, $isHtml)) {
$body .= ($body ? ($isHtml ? '<br/>' : "\n") : '') . $part_body;
}
}
}
else {
$body = self::compose_part_body(self::$MESSAGE, $isHtml);
}
// compose reply-body
if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
$body = self::create_reply_body($body, $isHtml);
if (!empty(self::$MESSAGE->pgp_mime)) {
$rcmail->output->set_env('compose_reply_header', self::get_reply_header(self::$MESSAGE));
}
}
// forward message body inline
else if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD) {
$body = self::create_forward_body($body, $isHtml);
}
// load draft message body
else if (
self::$COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT
|| self::$COMPOSE['mode'] == rcmail_sendmail::MODE_EDIT
) {
$body = self::create_draft_body($body, $isHtml);
}
// Save forwarded files (or inline images) as attachments
// This will also update inline images location in the body
self::write_compose_attachments(self::$MESSAGE, $isHtml, $body);
}
// new message
else {
$isHtml = self::compose_editor_mode();
}
$plugin = $rcmail->plugins->exec_hook('message_compose_body', [
'body' => $body,
'html' => $isHtml,
'mode' => self::$COMPOSE['mode'],
'message' => self::$MESSAGE,
]);
$body = $plugin['body'];
unset($plugin);
// add blocked.gif attachment (#1486516)
$regexp = '/ src="' . preg_quote($rcmail->output->asset_url('program/resources/blocked.gif'), '/') . '"/';
if ($isHtml && preg_match($regexp, $body)) {
$content = self::get_resource_content('blocked.gif');
if ($content && ($attachment = self::save_image('blocked.gif', 'image/gif', $content))) {
self::$COMPOSE['attachments'][$attachment['id']] = $attachment;
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$rcmail->comm_path, self::$COMPOSE['id'], $attachment['id']);
$body = preg_replace($regexp, ' src="' . $url . '"', $body);
}
}
self::$HTML_MODE = $isHtml;
return $body;
}
public static function compose_part_body($part, $isHtml = false)
{
// 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 = self::$MESSAGE->get_part_body($part->mime_id, true);
// message is cached but not exists (#1485443), or other error
if ($body === false) {
return '';
}
$rcmail = rcmail::get_instance();
// register this part as pgp encrypted
if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) {
self::$MESSAGE->pgp_mime = true;
$rcmail->output->set_env('pgp_mime_message', [
'_mbox' => $rcmail->storage->get_folder(),
'_uid' => self::$MESSAGE->uid,
'_part' => $part->mime_id,
]);
}
$strip_signature = self::$COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT
&& self::$COMPOSE['mode'] != rcmail_sendmail::MODE_EDIT
&& $rcmail->config->get('strip_existing_sig', true);
+ $flowed = !empty($part->ctype_parameters['format']) && $part->ctype_parameters['format'] == 'flowed';
+ $delsp = $flowed && !empty($part->ctype_parameters['delsp']) && $part->ctype_parameters['delsp'] == 'yes';
+
if ($isHtml) {
if ($part->ctype_secondary == 'html') {
$body = self::prepare_html_body($body);
}
else if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
}
else {
// try to remove the signature
if ($strip_signature) {
$body = self::remove_signature($body);
}
// add HTML formatting
- $body = self::plain_body($body,
- $part->ctype_parameters['format'] == 'flowed',
- $part->ctype_parameters['delsp'] == 'yes'
- );
+ $body = self::plain_body($body, $flowed, $delsp);
}
}
else {
if ($part->ctype_secondary == 'enriched') {
$body = rcube_enriched::to_html($body);
$part->ctype_secondary = 'html';
}
if ($part->ctype_secondary == 'html') {
// set line length for body wrapping
$line_length = $rcmail->config->get('line_length', 72);
// use html part if it has been used for message (pre)viewing
// decrease line length for quoting
$len = self::$COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY ? $line_length-2 : $line_length;
$body = $rcmail->html2text($body, ['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');
+ if ($part->ctype_secondary == 'plain' && $flowed) {
+ $body = rcube_mime::unfold_flowed($body, null, $delsp);
}
// try to remove the signature
if ($strip_signature) {
$body = self::remove_signature($body);
}
}
}
return $body;
}
public static function compose_body($attrib)
{
list($form_start, $form_end) = self::$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 (self::$HTML_MODE) {
$attrib['class'] = trim($attrib['class'] . ' mce_editor');
}
$attrib['name'] = '_message';
$rcmail = rcmail::get_instance();
$textarea = new html_textarea($attrib);
$hidden = new html_hiddenfield();
$hidden->add(['name' => '_draft_saveid', 'value' => $rcmail->output->get_env('draft_id')]);
$hidden->add(['name' => '_draft', 'value' => '']);
$hidden->add(['name' => '_is_html', 'value' => self::$HTML_MODE ? "1" : "0"]);
$hidden->add(['name' => '_framed', 'value' => '1']);
$rcmail->output->set_env('composebody', $attrib['id']);
// include HTML editor
self::html_editor();
return ($form_start ? "$form_start\n" : '')
. "\n" . $hidden->show() . "\n" . $textarea->show(self::$MESSAGE_BODY)
. ($form_end ? "\n$form_end\n" : '');
}
public static function create_reply_body($body, $bodyIsHtml)
{
$rcmail = rcmail::get_instance();
$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 = self::get_forward_header(self::$MESSAGE, $bodyIsHtml, false);
}
else {
$prefix = self::get_reply_header(self::$MESSAGE);
if ($bodyIsHtml) {
$prefix = '<p id="reply-intro">' . rcube::Q($prefix) . '</p>';
}
else {
$prefix .= "\n";
}
}
if (!$bodyIsHtml) {
// soft-wrap and quote message text
$line_length = $rcmail->config->get('line_length', 72);
$body = self::wrap_and_quote($body, $line_length, $reply_indent);
if ($reply_mode > 0) { // top-posting
$prefix = "\n\n\n" . $prefix;
$suffix = '';
}
else {
$suffix = "\n";
}
}
else {
$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;
}
public static function get_reply_header($message)
{
$rcmail = rcmail::get_instance();
- $from = array_pop(rcube_mime::decode_address_list($message->get_header('from'), 1, false, $message->headers->charset));
+ $list = rcube_mime::decode_address_list($message->get_header('from'), 1, false, $message->headers->charset);
+ $from = array_pop($list);
return $rcmail->gettext([
'name' => 'mailreplyintro',
'vars' => [
'date' => $rcmail->format_date($message->headers->date, $rcmail->config->get('date_long')),
'sender' => !empty($from['name']) ? $from['name'] : rcube_utils::idn_to_utf8($from['mailto']),
]
]);
}
public static function create_forward_body($body, $bodyIsHtml)
{
return self::get_forward_header(self::$MESSAGE, $bodyIsHtml) . trim($body, "\r\n");
}
public static function get_forward_header($message, $bodyIsHtml = false, $extended = true)
{
$rcmail = rcmail::get_instance();
$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;
}
public static function create_draft_body($body, $bodyIsHtml)
{
// Return the draft body as-is
return $body;
}
// Clean up HTML content of Draft/Reply/Forward (part of the message)
public static function prepare_html_body($body, $wash_params = [])
{
static $part_no;
// Set attributes of the part container
$container_id = self::$COMPOSE['mode'] . 'body' . (++$part_no);
$container_attrib = ['id' => $container_id];
$body_args = [
'safe' => self::$MESSAGE->is_safe,
'plain' => false,
'css_prefix' => 'v' . $part_no,
];
// remove comments (produced by washtml)
$replace = ['/<!--[^>]+-->/' => ''];
if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT) {
// convert TinyMCE's empty-line sequence (#1490463)
$replace['/<p>\xC2\xA0<\/p>/'] = '<p><br /></p>';
// remove <body> tags
$replace['/<body([^>]*)>/i'] = '';
$replace['/<\/body>/i'] = '';
}
else {
$body_args['container_id'] = $container_id;
$body_args['container_attrib'] = $container_attrib;
}
// Make the HTML content safe and clean
$body = self::wash_html($body, $wash_params + $body_args, self::$CID_MAP);
$body = preg_replace(array_keys($replace), array_values($replace), $body);
$body = self::html4inline($body, $body_args);
if (self::$COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT) {
$body = html::div($container_attrib, $body);
}
return $body;
}
// Removes signature from the message body
public static function remove_signature($body)
{
$rcmail = rcmail::get_instance();
$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", !empty($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;
}
public static function write_compose_attachments(&$message, $bodyIsHtml, &$message_body)
{
if (!empty($message->pgp_mime) || !empty(self::$COMPOSE['forward_attachments'])) {
return;
}
$messages = [];
$loaded_attachments = [];
if (!empty(self::$COMPOSE['attachments'])) {
foreach ((array) self::$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
}
}
$rcmail = rcmail::get_instance();
$has_html = $message->has_html_part();
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
|| $part->mimetype == 'message/rfc822'
) {
// 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' && self::$COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
continue;
}
// skip version.txt parts of multipart/encrypted messages
if (!empty($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;
}
}
$replace = null;
// Skip inline images when not used in the body
// Note: Apple Mail sends PDF files marked as inline (#7382)
// Note: Apple clients send inline images even if there's no HTML body (#7414)
if ($has_html && $part->disposition == 'inline' && $part->mimetype != 'application/pdf') {
if (!$bodyIsHtml) {
continue;
}
$idx = $part->content_id ? ('cid:' . $part->content_id) : $part->content_location;
if ($idx && isset(self::$CID_MAP[$idx]) && strpos($message_body, self::$CID_MAP[$idx]) !== false) {
$replace = self::$CID_MAP[$idx];
}
else {
continue;
}
}
// skip any other attachment on Reply
else if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
continue;
}
$key = self::attachment_name($part) . $part->mimetype;
if (!empty($loaded_attachments[$key])) {
$attachment = $loaded_attachments[$key];
}
else {
$attachment = self::save_attachment($message, $pid, self::$COMPOSE['id']);
}
if ($attachment) {
if ($replace) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$rcmail->comm_path, self::$COMPOSE['id'], $attachment['id']);
$message_body = str_replace($replace, $url, $message_body);
}
}
}
}
self::$COMPOSE['forward_attachments'] = true;
}
/**
* Create a map of attachment content-id/content-locations
*/
public static function cid_map($message)
{
if (!empty($message->pgp_mime)) {
return [];
}
$messages = [];
$map = [];
foreach ((array) $message->mime_parts() as $pid => $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}
if (!empty($part->content_id) || !empty($part->content_location)) {
// skip attachments included in message/rfc822 attachment (#1486487, #1490607)
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}
$url = sprintf('RCMAP%s', md5($message->folder . '/' . $message->uid . '/' . $pid));
$idx = !empty($part->content_id) ? ('cid:' . $part->content_id) : $part->content_location;
$map[$idx] = $url;
}
}
return $map;
}
// Creates attachment(s) from the forwarded message(s)
public static function write_forward_attachments()
{
if (!empty(self::$MESSAGE->pgp_mime)) {
return;
}
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$names = [];
$refs = [];
$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 = [];
if (!empty(self::$COMPOSE['attachments'])) {
foreach ((array) self::$COMPOSE['attachments'] as $attachment) {
$loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment;
$total_size += $attachment['size'];
}
}
if (self::$COMPOSE['forward_uid'] == '*') {
$index = $storage->index(null, self::sort_column(), self::sort_order());
self::$COMPOSE['forward_uid'] = $index->get();
}
else if (!is_array(self::$COMPOSE['forward_uid']) && strpos(self::$COMPOSE['forward_uid'], ':')) {
self::$COMPOSE['forward_uid'] = rcube_imap_generic::uncompressMessageSet(self::$COMPOSE['forward_uid']);
}
else if (is_string(self::$COMPOSE['forward_uid'])) {
self::$COMPOSE['forward_uid'] = explode(',', self::$COMPOSE['forward_uid']);
}
foreach ((array) self::$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(self::$MESSAGE->subject)) {
self::$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;
self::save_attachment($message, null, self::$COMPOSE['id'], ['filename' => $name]);
if ($message->headers->messageID) {
$refs[] = $message->headers->messageID;
}
}
// set In-Reply-To and References headers
if (count($refs) == 1) {
self::$COMPOSE['reply_msgid'] = $refs[0];
}
if (!empty($refs)) {
self::$COMPOSE['references'] = implode(' ', $refs);
}
if ($size_errors) {
$limit = self::show_bytes($size_limit);
$error = $rcmail->gettext([
'name' => 'msgsizeerrorfwd',
'vars' => ['num' => $size_errors, 'size' => $limit]
]);
$script = sprintf("%s.display_message('%s', 'error');", rcmail_output::JS_OBJECT_NAME, rcube::JQ($error));
$rcmail->output->add_script($script, 'docready');
}
}
/**
* Saves an image as attachment
*/
public static function save_image($path, $mimetype = '', $data = null)
{
$is_file = false;
// handle attachments in memory
if (empty($data)) {
$data = file_get_contents($path);
$is_file = true;
}
$name = self::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 = [
'group' => self::$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()
*/
public static function 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
*/
public static function compose_attachment_list($attrib)
{
// add ID if not given
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmAttachmentList';
}
$rcmail = rcmail::get_instance();
$out = '';
$button = '';
$jslist = [];
if (!empty($attrib['icon_pos']) && $attrib['icon_pos'] == 'left') {
self::$COMPOSE['icon_pos'] = 'left';
}
$icon_pos = isset(self::$COMPOSE['icon_pos']) ? self::$COMPOSE['icon_pos'] : null;
if (!empty(self::$COMPOSE['attachments'])) {
if (!empty($attrib['deleteicon'])) {
$button = html::img([
'src' => $rcmail->output->asset_url($attrib['deleteicon'], true),
'alt' => $rcmail->gettext('delete')
]);
}
else if (self::get_bool_attr($attrib, 'textbuttons')) {
$button = rcube::Q($rcmail->gettext('delete'));
}
foreach (self::$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']),
self::show_bytes($a_prop['size'])
);
$content_link = html::a([
'href' => '#load',
'class' => 'filename',
'onclick' => sprintf(
"return %s.command('load-attachment','rcmfile%s', this, event)",
rcmail_output::JS_OBJECT_NAME,
$id
),
'tabindex' => !empty($attrib['tabindex']) ? $attrib['tabindex'] : '0',
],
$link_content
);
$delete_link = html::a([
'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' => !empty($attrib['tabindex']) ? $attrib['tabindex'] : '0',
'aria-label' => $rcmail->gettext('delete') . ' ' . $a_prop['name'],
],
$button
);
$out .= html::tag('li', [
'id' => 'rcmfile' . $id,
'class' => rcube_utils::file2class($a_prop['mimetype'], $a_prop['name']),
],
$icon_pos == 'left' ? $delete_link.$content_link : $content_link.$delete_link
);
$jslist['rcmfile'.$id] = [
'name' => $a_prop['name'],
'complete' => true,
'mimetype' => $a_prop['mimetype']
];
}
}
if (!empty($attrib['deleteicon'])) {
self::$COMPOSE['deleteicon'] = $rcmail->output->asset_url($attrib['deleteicon'], true);
}
else if (self::get_bool_attr($attrib, 'textbuttons')) {
self::$COMPOSE['textbuttons'] = true;
}
if (!empty($attrib['cancelicon'])) {
$rcmail->output->set_env('cancelicon', $rcmail->output->asset_url($attrib['cancelicon'], true));
}
if (!empty($attrib['loadingicon'])) {
$rcmail->output->set_env('loadingicon', $rcmail->output->asset_url($attrib['loadingicon'], true));
}
$rcmail->output->set_env('attachments', $jslist);
$rcmail->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
*/
public static function compose_attachment_form($attrib)
{
$rcmail = rcmail::get_instance();
// Limit attachment size according to message size limit
$limit = parse_bytes($rcmail->config->get('max_message_size')) / 1.33;
return self::upload_form($attrib, 'uploadform', 'send-attachment', ['multiple' => true], $limit);
}
/**
* Register a certain container as active area to drop files onto
*/
public static function compose_file_drop_area($attrib)
{
$rcmail = rcmail::get_instance();
if (!empty($attrib['id'])) {
$rcmail->output->add_gui_object('filedrop', $attrib['id']);
$rcmail->output->set_env('filedrop', ['action' => 'upload', 'fieldname' => '_attachments']);
}
}
/**
* Editor mode selector object for templates
*/
public static function editor_selector($attrib)
{
$rcmail = rcmail::get_instance();
// determine whether HTML or plain text should be checked
$useHtml = self::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
*/
public static function addressbook_list($attrib = [])
{
$rcmail = rcmail::get_instance();
$attrib += ['id' => 'rcmdirectorylist'];
$line_templ = html::tag('li',
['id' => 'rcmli%s', 'class' => '%s'],
html::a([
'href' => '#list',
'rel' => '%s',
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list-addresses','%s',this)"
],
'%s'
)
);
$out = '';
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 (!empty($source['class_name'])) {
$class_name .= ' ' . $source['class_name'];
}
$out .= sprintf($line_templ,
rcube_utils::html_identifier($id,true),
$class_name,
$source['id'],
$js_id,
!empty($source['name']) ? $source['name'] : $id
);
}
$rcmail->output->add_gui_object('addressbookslist', $attrib['id']);
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
/**
* Contacts list object for templates
*/
public static function contacts_list($attrib = [])
{
$rcmail = rcmail::get_instance();
$attrib += ['id' => 'rcmAddressList'];
// set client env
$rcmail->output->add_gui_object('contactslist', $attrib['id']);
$rcmail->output->set_env('pagecount', 0);
$rcmail->output->set_env('current_page', 0);
$rcmail->output->include_script('list.js');
return $rcmail->table_output($attrib, [], ['name'], 'ID');
}
/**
* Responses list object for templates
*/
public static function compose_responses_list($attrib)
{
$rcmail = rcmail::get_instance();
$attrib += ['id' => 'rcmresponseslist', 'tagname' => 'ul', 'cols' => 1];
$jsenv = [];
$list = new html_table($attrib);
foreach ($rcmail->get_compose_responses(true) as $response) {
$key = $response['key'];
$item = html::a([
'href' => '#' . urlencode($response['name']),
'class' => rtrim('insertresponse ' . $attrib['itemclass']),
'unselectable' => 'on',
'tabindex' => '0',
'rel' => $key,
],
rcube::Q($response['name'])
);
$jsenv[$key] = $response;
$list->add([], $item);
}
// set client env
$rcmail->output->set_env('textresponses', $jsenv);
$rcmail->output->add_gui_object('responseslist', $attrib['id']);
return $list->show();
}
public static function save_attachment($message, $pid, $compose_id, $params = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
if ($pid) {
// attachment requested
$part = $message->mime_parts[$pid];
$size = $part->size;
$mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
$filename = !empty($params['filename']) ? $params['filename'] : self::attachment_name($part);
}
else if ($message instanceof rcube_message) {
// the whole message requested
$size = isset($message->size) ? $message->size : null;
$mimetype = 'message/rfc822';
$filename = !empty($params['filename']) ? $params['filename'] : 'message_rfc822.eml';
}
else if (is_string($message)) {
// the whole message requested
$size = strlen($message);
$data = $message;
$mimetype = $params['mimetype'];
$filename = $params['filename'];
}
else {
return;
}
if (!isset($data)) {
$data = null;
$path = null;
// don't load too big attachments into memory
if (!rcube_utils::mem_check($size)) {
$path = rcube_utils::temp_filename('attmnt');
if ($fp = fopen($path, 'w')) {
if ($pid) {
// part body
$message->get_part_body($pid, false, 0, $fp);
}
else {
// complete message
$storage->get_raw_body($message->uid, $fp);
}
fclose($fp);
}
else {
return false;
}
}
else if ($pid) {
// part body
$data = $message->get_part_body($pid);
}
else {
// complete message
$data = $storage->get_raw_body($message->uid);
}
}
$attachment = [
'group' => $compose_id,
'name' => $filename,
'mimetype' => $mimetype,
'content_id' => !empty($part) && isset($part->content_id) ? $part->content_id : null,
'data' => $data,
'path' => isset($path) ? $path : null,
'size' => isset($path) ? filesize($path) : strlen($data),
'charset' => !empty($part) ? $part->charset : (isset($params['charset']) ? $params['charset'] : null),
];
$attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
// rcube_session::append() replaces current session data with the old values
// (in rcube_session::reload()). This is a problem in 'compose' action, because before
// the first append() use we set some important data in the session.
// It also overwrites attachments list. Fixing reload() is not so simple if possible
// as we don't really know what has been added and what removed in meantime.
// So, for now we'll do not use append() on 'compose' action (#1490608).
if ($rcmail->action == 'compose') {
self::$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
else {
$rcmail->session->append('compose_data_' . $compose_id . '.attachments', $attachment['id'], $attachment);
}
return $attachment;
}
else if (!empty($path)) {
@unlink($path);
}
return false;
}
}
diff --git a/program/actions/mail/delete.php b/program/actions/mail/delete.php
index cffa542f2..bf9e3b9b9 100644
--- a/program/actions/mail/delete.php
+++ b/program/actions/mail/delete.php
@@ -1,140 +1,140 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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: |
| Handler for mail delete operation |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_delete extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// count messages before changing anything
$threading = (bool) $rcmail->storage->get_threading();
$trash = $rcmail->config->get('trash_mbox');
$sources = [];
$old_count = 0;
$deleted = 0;
$count = 0;
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
}
if (empty($_POST['_uid'])) {
$rcmail->output->show_message('internalerror', 'error');
$rcmail->output->send();
}
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
$deleted += (int) $rcmail->storage->delete_message($uids, $mbox);
$count += is_array($uids) ? count($uids) : 1;
$sources[] = $mbox;
}
if (empty($deleted)) {
// send error message
if ($_POST['_from'] != 'show') {
$rcmail->output->command('list_mailbox');
}
self::display_server_error('errordeleting');
$rcmail->output->send();
}
else {
$rcmail->output->show_message('messagedeleted', 'confirmation');
}
$search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC);
// refresh saved search set after moving some messages
if ($search_request && $rcmail->storage->get_search_set()) {
$_SESSION['search'] = $rcmail->storage->refresh_search();
}
- if ($_POST['_from'] == 'show') {
+ if (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC)) {
$rcmail->output->command('show_message', $next);
}
else {
$rcmail->output->command('command', 'list');
}
$rcmail->output->send();
}
$mbox = $rcmail->storage->get_folder();
$msg_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
$exists = $rcmail->storage->count($mbox, 'EXISTS', true);
$page_size = $rcmail->storage->get_pagesize();
$page = $rcmail->storage->get_page();
$pages = ceil($msg_count / $page_size);
$nextpage_count = $old_count - $page_size * $page;
$remaining = $msg_count - $page_size * ($page - 1);
$jump_back = false;
// jump back one page (user removed the whole last page)
if ($page > 1 && $remaining == 0) {
$page -= 1;
$rcmail->storage->set_page($page);
$_SESSION['page'] = $page;
$jump_back = true;
}
// update unseen messages counts for all involved folders
foreach ($sources as $source) {
self::send_unread_count($source, true);
}
// update message count display
$rcmail->output->set_env('messagecount', $msg_count);
$rcmail->output->set_env('current_page', $page);
$rcmail->output->set_env('pagecount', $pages);
$rcmail->output->set_env('exists', $exists);
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
if ($threading) {
$count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST);
}
// add new rows from next page (if any)
if (!empty($count) && $_POST['_uid'] != '*' && ($jump_back || $nextpage_count > 0)) {
// #5862: Don't add more rows than it was on the next page
$count = $jump_back ? null : min($nextpage_count, $count);
$a_headers = $rcmail->storage->list_messages($mbox, null, self::sort_column(), self::sort_order(), $count);
self::js_message_list($a_headers, false);
}
// set trash folder state
if ($mbox === $trash) {
$rcmail->output->command('set_trash_count', $exists);
}
// send response
$rcmail->output->send();
}
}
diff --git a/program/include/rcmail_action.php b/program/include/rcmail_action.php
index 1c965ecd9..57bf9fd08 100644
--- a/program/include/rcmail_action.php
+++ b/program/include/rcmail_action.php
@@ -1,1478 +1,1478 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| 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: |
| An abstract for HTTP request handlers with some helpers. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* An abstract for HTTP request handlers with some helpers.
*
* @package Webmail
*/
abstract class rcmail_action
{
const MODE_AJAX = 1;
const MODE_HTTP = 2;
/**
* Mode of operation supported by the action. Use MODE_* constants.
* By default all modes are allowed.
*
* @var int
*/
protected static $mode;
/**
* A name of a initialized common form
*
* @var string
*/
protected static $edit_form;
/**
* Deprecated action aliases.
*
* @todo Get rid of these (but it will be a big BC break)
* @var array
*/
public static $aliases = [];
/**
* Request handler. The only abstract method.
*
* @param array $args Arguments from the previous step(s)
*/
abstract public function run($args = []);
/**
* Request sanity checks, e.g. supported request mode
*
* @return bool
*/
public function checks()
{
$rcmail = rcmail::get_instance();
if (static::$mode) {
if (!(static::$mode & self::MODE_HTTP) && empty($rcmail->output->ajax_call)) {
return false;
}
if (!(static::$mode & self::MODE_AJAX) && !empty($rcmail->output->ajax_call)) {
return false;
}
}
return true;
}
/**
* Set environment variables for specified config boolean options
*
* @param array $options List of configuration option names
*/
public static function set_env_config($options)
{
$rcmail = rcmail::get_instance();
foreach ((array) $options as $option) {
if ($rcmail->config->get($option)) {
$rcmail->output->set_env($option, true);
}
}
}
/**
* Create a HTML table based on the given data
*
* @param array $attrib Named table attributes
* @param mixed $table_data Table row data. Either a two-dimensional array
* or a valid SQL result set
* @param array $show_cols List of cols to show
* @param string $id_col Name of the identifier col
*
* @return string HTML table code
*/
public static function table_output($attrib, $table_data, $show_cols, $id_col)
{
$rcmail = rcmail::get_instance();
$table = new html_table($attrib);
// add table header
if (empty($attrib['noheader'])) {
foreach ($show_cols as $col) {
$table->add_header($col, rcube::Q($rcmail->gettext($col)));
}
}
if (!is_array($table_data)) {
$db = $rcmail->get_dbh();
while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
$table->add_row(['id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])]);
// format each col
foreach ($show_cols as $col) {
$table->add($col, rcube::Q($sql_arr[$col]));
}
}
}
else {
foreach ($table_data as $row_data) {
$class = !empty($row_data['class']) ? $row_data['class'] : null;
if (!empty($attrib['rowclass'])) {
$class = trim($class . ' ' . $attrib['rowclass']);
}
$rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]);
$table->add_row(['id' => $rowid, 'class' => $class]);
// format each col
foreach ($show_cols as $col) {
$val = is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col];
$table->add($col, empty($attrib['ishtml']) ? rcube::Q($val) : $val);
}
}
}
return $table->show($attrib);
}
/**
* Return HTML for quota indicator object
*
* @param array $attrib Named parameters
*
* @return string HTML code for the quota indicator object
*/
public static function quota_display($attrib)
{
$rcmail = rcmail::get_instance();
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmquotadisplay';
}
$_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
$quota = self::quota_content($attrib);
$rcmail->output->add_gui_object('quotadisplay', $attrib['id']);
$rcmail->output->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
return html::span($attrib, ' ');
}
/**
* Return (parsed) quota information
*
* @param array $attrib Named parameters
* @param array $folder Current folder
*
* @return array Quota information
*/
public static function quota_content($attrib = null, $folder = null)
{
$rcmail = rcmail::get_instance();
$quota = $rcmail->storage->get_quota($folder);
$quota = $rcmail->plugins->exec_hook('quota', $quota);
$quota_result = (array) $quota;
$quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
$quota_result['folder'] = $folder !== null && $folder !== '' ? $folder : 'INBOX';
if (!empty($quota['total']) && $quota['total'] > 0) {
if (!isset($quota['percent'])) {
$quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
}
$title = $rcmail->gettext('quota') . ': ' . sprintf('%s / %s (%.0f%%)',
self::show_bytes($quota['used'] * 1024),
self::show_bytes($quota['total'] * 1024),
$quota_result['percent']
);
$quota_result['title'] = $title;
if (!empty($attrib['width'])) {
$quota_result['width'] = $attrib['width'];
}
if (!empty($attrib['height'])) {
$quota_result['height'] = $attrib['height'];
}
// build a table of quota types/roots info
if (($root_cnt = count($quota_result['all'])) > 1 || count($quota_result['all'][key($quota_result['all'])]) > 1) {
$table = new html_table(['cols' => 3, 'class' => 'quota-info']);
$table->add_header(null, rcube::Q($rcmail->gettext('quotatype')));
$table->add_header(null, rcube::Q($rcmail->gettext('quotatotal')));
$table->add_header(null, rcube::Q($rcmail->gettext('quotaused')));
foreach ($quota_result['all'] as $root => $data) {
if ($root_cnt > 1 && $root) {
$table->add(['colspan' => 3, 'class' => 'root'], rcube::Q($root));
}
if ($storage = $data['storage']) {
$percent = min(100, round(($storage['used']/max(1,$storage['total']))*100));
$table->add('name', rcube::Q($rcmail->gettext('quotastorage')));
$table->add(null, self::show_bytes($storage['total'] * 1024));
$table->add(null, sprintf('%s (%.0f%%)', self::show_bytes($storage['used'] * 1024), $percent));
}
if ($message = $data['message']) {
$percent = min(100, round(($message['used']/max(1,$message['total']))*100));
$table->add('name', rcube::Q($rcmail->gettext('quotamessage')));
$table->add(null, intval($message['total']));
$table->add(null, sprintf('%d (%.0f%%)', $message['used'], $percent));
}
}
$quota_result['table'] = $table->show();
}
}
else {
$unlimited = $rcmail->config->get('quota_zero_as_unlimited');
$quota_result['title'] = $rcmail->gettext($unlimited ? 'unlimited' : 'unknown');
$quota_result['percent'] = 0;
}
// cleanup
unset($quota_result['abort']);
if (empty($quota_result['table'])) {
unset($quota_result['all']);
}
return $quota_result;
}
/**
* Outputs error message according to server error/response codes
*
* @param string $fallback Fallback message label
* @param array $fallback_args Fallback message label arguments
* @param string $suffix Message label suffix
* @param array $params Additional parameters (type, prefix)
*/
public static function display_server_error($fallback = null, $fallback_args = null, $suffix = '', $params = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$err_code = $storage->get_error_code();
$res_code = $storage->get_response_code();
$args = [];
if ($res_code == rcube_storage::NOPERM) {
$error = 'errornoperm';
}
else if ($res_code == rcube_storage::READONLY) {
$error = 'errorreadonly';
}
else if ($res_code == rcube_storage::OVERQUOTA) {
$error = 'erroroverquota';
}
else if ($err_code && ($err_str = $storage->get_error_str())) {
// try to detect access rights problem and display appropriate message
if (stripos($err_str, 'Permission denied') !== false) {
$error = 'errornoperm';
}
// try to detect full mailbox problem and display appropriate message
// there can be e.g. "Quota exceeded" / "quotum would exceed" / "Over quota"
else if (stripos($err_str, 'quot') !== false && preg_match('/exceed|over/i', $err_str)) {
$error = 'erroroverquota';
}
else {
$error = 'servererrormsg';
$args = ['msg' => rcube::Q($err_str)];
}
}
else if ($err_code < 0) {
$error = 'storageerror';
}
else if ($fallback) {
$error = $fallback;
$args = $fallback_args;
$params['prefix'] = false;
}
if (!empty($error)) {
if ($suffix && $rcmail->text_exists($error . $suffix)) {
$error .= $suffix;
}
$msg = $rcmail->gettext(['name' => $error, 'vars' => $args]);
if (!empty($params['prefix']) && $fallback) {
$msg = $rcmail->gettext(['name' => $fallback, 'vars' => $fallback_args]) . ' ' . $msg;
}
$rcmail->output->show_message($msg, !empty($params['type']) ? $params['type'] : 'error');
}
}
/**
* Displays an error message on storage fatal errors
*/
public static function storage_fatal_error()
{
$rcmail = rcmail::get_instance();
$err_code = $rcmail->storage->get_error_code();
switch ($err_code) {
// Not all are really fatal, but these should catch
// connection/authentication errors the best we can
case rcube_imap_generic::ERROR_NO:
case rcube_imap_generic::ERROR_BAD:
case rcube_imap_generic::ERROR_BYE:
self::display_server_error();
}
}
/**
* Output HTML editor scripts
*
* @param string $mode Editor mode
*/
public static function html_editor($mode = '')
{
$rcmail = rcmail::get_instance();
$spellcheck = intval($rcmail->config->get('enable_spellcheck'));
$spelldict = intval($rcmail->config->get('spellcheck_dictionary'));
$disabled_plugins = [];
$disabled_buttons = [];
$extra_plugins = [];
$extra_buttons = [];
if (!$spellcheck) {
$disabled_plugins[] = 'spellchecker';
}
$hook = $rcmail->plugins->exec_hook('html_editor', [
'mode' => $mode,
'disabled_plugins' => $disabled_plugins,
'disabled_buttons' => $disabled_buttons,
'extra_plugins' => $extra_plugins,
'extra_buttons' => $extra_buttons,
]);
if (!empty($hook['abort'])) {
return;
}
$lang_codes = [$_SESSION['language']];
$assets_dir = $rcmail->config->get('assets_dir') ?: INSTALL_PATH;
$skin_path = $rcmail->output->get_skin_path();
if ($pos = strpos($_SESSION['language'], '_')) {
$lang_codes[] = substr($_SESSION['language'], 0, $pos);
}
foreach ($lang_codes as $code) {
if (file_exists("$assets_dir/program/js/tinymce/langs/$code.js")) {
$lang = $code;
break;
}
}
if (empty($lang)) {
$lang = 'en';
}
$config = [
'mode' => $mode,
'lang' => $lang,
'skin_path' => $skin_path,
'spellcheck' => $spellcheck, // deprecated
'spelldict' => $spelldict,
'content_css' => 'program/resources/tinymce/content.css',
'disabled_plugins' => $hook['disabled_plugins'],
'disabled_buttons' => $hook['disabled_buttons'],
'extra_plugins' => $hook['extra_plugins'],
'extra_buttons' => $hook['extra_buttons'],
];
if ($path = $rcmail->config->get('editor_css_location')) {
if ($path = $rcmail->find_asset($skin_path . $path)) {
$config['content_css'] = $path;
}
}
$font_family = $rcmail->output->get_env('default_font');
$font_size = $rcmail->output->get_env('default_font_size');
$style = [];
if ($font_family) {
$style[] = "font-family: $font_family;";
}
if ($font_size) {
$style[] = "font-size: $font_size;";
}
if (!empty($style)) {
$config['content_style'] = "body {" . implode(' ', $style) . "}";
}
$rcmail->output->set_env('editor_config', $config);
$rcmail->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia', 'close');
if ($path = $rcmail->config->get('media_browser_css_location', 'program/resources/tinymce/browser.css')) {
if ($path != 'none' && ($path = $rcmail->find_asset($path))) {
$rcmail->output->include_css($path);
}
}
$rcmail->output->include_script('tinymce/tinymce.min.js');
$rcmail->output->include_script('editor.js');
}
/**
* File upload progress handler.
*
* @deprecated We're using HTML5 upload progress
*/
public static function upload_progress()
{
// NOOP
rcmail::get_instance()->output->send();
}
/**
* Initializes file uploading interface.
*
* @param int $max_size Optional maximum file size in bytes
*
* @return string Human-readable file size limit
*/
public static function upload_init($max_size = null)
{
$rcmail = rcmail::get_instance();
// find max filesize value
$max_filesize = rcube_utils::max_upload_size();
if ($max_size && $max_size < $max_filesize) {
$max_filesize = $max_size;
}
$max_filesize_txt = self::show_bytes($max_filesize);
$rcmail->output->set_env('max_filesize', $max_filesize);
$rcmail->output->set_env('filesizeerror', $rcmail->gettext([
'name' => 'filesizeerror', 'vars' => ['size' => $max_filesize_txt]]));
if ($max_filecount = ini_get('max_file_uploads')) {
$rcmail->output->set_env('max_filecount', $max_filecount);
$rcmail->output->set_env('filecounterror', $rcmail->gettext([
'name' => 'filecounterror', 'vars' => ['count' => $max_filecount]]));
}
$rcmail->output->add_label('uploadprogress', 'GB', 'MB', 'KB', 'B');
return $max_filesize_txt;
}
/**
* Upload form object
*
* @param array $attrib Object attributes
* @param string $name Form object name
* @param string $action Form action name
* @param array $input_attr File input attributes
* @param int $max_size Maximum upload size
*
* @return string HTML output
*/
public static function upload_form($attrib, $name, $action, $input_attr = [], $max_size = null)
{
$rcmail = rcmail::get_instance();
// Get filesize, enable upload progress bar
$max_filesize = self::upload_init($max_size);
$hint = html::div('hint', $rcmail->gettext(['name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]));
if (!empty($attrib['mode']) && $attrib['mode'] == 'hint') {
return $hint;
}
// set defaults
$attrib += ['id' => 'rcmUploadbox', 'buttons' => 'yes'];
$event = rcmail_output::JS_OBJECT_NAME . ".command('$action', this.form)";
$form_id = $attrib['id'] . 'Frm';
// Default attributes of file input and form
$input_attr += [
'id' => $attrib['id'] . 'Input',
'type' => 'file',
'name' => '_attachments[]',
'class' => 'form-control',
];
$form_attr = [
'id' => $form_id,
'name' => $name,
'method' => 'post',
'enctype' => 'multipart/form-data'
];
if (!empty($attrib['mode']) && $attrib['mode'] == 'smart') {
unset($attrib['buttons']);
$form_attr['class'] = 'smart-upload';
$input_attr = array_merge($input_attr, [
// #5854: Chrome does not execute onchange when selecting the same file.
// To fix this we reset the input using null value.
'onchange' => "$event; this.value=null",
'class' => 'smart-upload',
'tabindex' => '-1',
]);
}
$input = new html_inputfield($input_attr);
$content = (isset($attrib['prefix']) ? $attrib['prefix'] : '') . $input->show();
if (empty($attrib['mode']) || $attrib['mode'] != 'smart') {
$content = html::div(null, $content . $hint);
}
if (self::get_bool_attr($attrib, 'buttons')) {
$button = new html_inputfield(['type' => 'button']);
$content .= html::div('buttons',
$button->show($rcmail->gettext('close'), ['class' => 'button', 'onclick' => "$('#{$attrib['id']}').hide()"])
. ' ' .
$button->show($rcmail->gettext('upload'), ['class' => 'button mainaction', 'onclick' => $event])
);
}
$rcmail->output->add_gui_object($name, $form_id);
return html::div($attrib, $rcmail->output->form_tag($form_attr, $content));
}
/**
* Common file upload error handler
*
* @param int $php_error PHP error from $_FILES
* @param array $attachment Attachment data from attachment_upload hook
* @param string $add_error Additional error label (highest prio)
*/
public static function upload_error($php_error, $attachment = null, $add_error = null)
{
$rcmail = rcmail::get_instance();
if ($add_error) {
$msg = $rcmail->gettext($add_error);
}
else if ($attachment && !empty($attachment['error'])) {
$msg = $attachment['error'];
}
else if ($php_error == UPLOAD_ERR_INI_SIZE || $php_error == UPLOAD_ERR_FORM_SIZE) {
$post_size = self::show_bytes(rcube_utils::max_upload_size());
$msg = $rcmail->gettext(['name' => 'filesizeerror', 'vars' => ['size' => $post_size]]);
}
else {
$msg = $rcmail->gettext('fileuploaderror');
}
$rcmail->output->command('display_message', $msg, 'error');
}
/**
* Common POST file upload error handler
*
* @return bool True if it was a POST request, False otherwise
*/
public static function upload_failure()
{
if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] != 'POST') {
return false;
}
$rcmail = rcmail::get_instance();
// if filesize exceeds post_max_size then $_FILES array is empty,
// show filesizeerror instead of fileuploaderror
if ($maxsize = ini_get('post_max_size')) {
$msg = $rcmail->gettext([
'name' => 'filesizeerror',
'vars' => ['size' => self::show_bytes(parse_bytes($maxsize))]
]);
}
else {
$msg = $rcmail->gettext('fileuploaderror');
}
$rcmail->output->command('display_message', $msg, 'error');
return true;
}
/**
* Outputs uploaded file content (with image thumbnails support
*
* @param array $file Uploaded file data
*/
public static function display_uploaded_file($file)
{
if (empty($file)) {
return;
}
$rcmail = rcmail::get_instance();
$file = $rcmail->plugins->exec_hook('attachment_display', $file);
if (!empty($file['status'])) {
if (empty($file['size'])) {
$file['size'] = !empty($file['data']) ? strlen($file['data']) : @filesize($file['path']);
}
// generate image thumbnail for file browser in HTML editor
if (!empty($_GET['_thumbnail'])) {
$thumbnail_size = 80;
$mimetype = $file['mimetype'];
$file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size'];
$thumb_name = 'thumb' . md5($file_ident . ':' . $rcmail->user->ID . ':' . $thumbnail_size);
$cache_file = rcube_utils::temp_filename($thumb_name, false, false);
// render thumbnail image if not done yet
if (!is_file($cache_file)) {
if (!$file['path']) {
$orig_name = $filename = $cache_file . '.tmp';
file_put_contents($orig_name, $file['data']);
}
else {
$filename = $file['path'];
}
$image = new rcube_image($filename);
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
$mimetype = 'image/' . $imgtype;
if (!empty($orig_name)) {
unlink($orig_name);
}
}
}
if (is_file($cache_file)) {
// cache for 1h
$rcmail->output->future_expire_header(3600);
header('Content-Type: ' . $mimetype);
header('Content-Length: ' . filesize($cache_file));
readfile($cache_file);
exit;
}
}
header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);
if ($file['data']) {
echo $file['data'];
}
else if ($file['path']) {
readfile($file['path']);
}
}
}
/**
* Initializes client-side autocompletion.
*/
public static function autocomplete_init()
{
static $init;
if ($init) {
return;
}
$init = 1;
$rcmail = rcmail::get_instance();
if (($threads = (int) $rcmail->config->get('autocomplete_threads')) > 0) {
$book_types = (array) $rcmail->config->get('autocomplete_addressbooks', 'sql');
if (count($book_types) > 1) {
$rcmail->output->set_env('autocomplete_threads', $threads);
$rcmail->output->set_env('autocomplete_sources', $book_types);
}
}
$rcmail->output->set_env('autocomplete_max', (int) $rcmail->config->get('autocomplete_max', 15));
$rcmail->output->set_env('autocomplete_min_length', $rcmail->config->get('autocomplete_min_length'));
$rcmail->output->add_label('autocompletechars', 'autocompletemore');
}
/**
* Returns supported font-family specifications
*
* @param string $font Font name
*
* @return string|array Font-family specification array or string (if $font is used)
*/
public static function font_defs($font = null)
{
$fonts = [
'Andale Mono' => '"Andale Mono",Times,monospace',
'Arial' => 'Arial,Helvetica,sans-serif',
'Arial Black' => '"Arial Black","Avant Garde",sans-serif',
'Book Antiqua' => '"Book Antiqua",Palatino,serif',
'Courier New' => '"Courier New",Courier,monospace',
'Georgia' => 'Georgia,Palatino,serif',
'Helvetica' => 'Helvetica,Arial,sans-serif',
'Impact' => 'Impact,Chicago,sans-serif',
'Tahoma' => 'Tahoma,Arial,Helvetica,sans-serif',
'Terminal' => 'Terminal,Monaco,monospace',
'Times New Roman' => '"Times New Roman",Times,serif',
'Trebuchet MS' => '"Trebuchet MS",Geneva,sans-serif',
'Verdana' => 'Verdana,Geneva,sans-serif',
];
if ($font) {
return !empty($fonts[$font]) ? $fonts[$font] : null;
}
return $fonts;
}
/**
* Create a human readable string for a number of bytes
*
* @param int $bytes Number of bytes
* @param string &$unit Size unit
*
* @return string Byte string
*/
public static function show_bytes($bytes, &$unit = null)
{
$rcmail = rcmail::get_instance();
// Plugins may want to display different units
$plugin = $rcmail->plugins->exec_hook('show_bytes', ['bytes' => $bytes, 'unit' => null]);
$unit = $plugin['unit'];
if (isset($plugin['result'])) {
return $plugin['result'];
}
if ($bytes >= 1073741824) {
$unit = 'GB';
$gb = $bytes/1073741824;
$str = sprintf($gb >= 10 ? "%d " : "%.1f ", $gb) . $rcmail->gettext($unit);
}
else if ($bytes >= 1048576) {
$unit = 'MB';
$mb = $bytes/1048576;
$str = sprintf($mb >= 10 ? "%d " : "%.1f ", $mb) . $rcmail->gettext($unit);
}
else if ($bytes >= 1024) {
$unit = 'KB';
$str = sprintf("%d ", round($bytes/1024)) . $rcmail->gettext($unit);
}
else {
$unit = 'B';
$str = sprintf('%d ', $bytes) . $rcmail->gettext($unit);
}
return $str;
}
/**
* Returns real size (calculated) of the message part
*
* @param rcube_message_part $part Message part
*
* @return string Part size (and unit)
*/
public static function message_part_size($part)
{
if (isset($part->d_parameters['size'])) {
$size = self::show_bytes((int) $part->d_parameters['size']);
}
else {
$size = $part->size;
if ($size === 0) {
$part->exact_size = true;
}
if (isset($part->encoding) && $part->encoding == 'base64') {
$size = $size / 1.33;
}
$size = self::show_bytes($size);
}
if (empty($part->exact_size)) {
$size = '~' . $size;
}
return $size;
}
/**
* Returns message UID(s) and IMAP folder(s) from GET/POST data
*
* @param string $uids UID value to decode
* @param string $mbox Default mailbox value (if not encoded in UIDs)
* @param bool $is_multifolder Will be set to True if multi-folder request
* @param int $mode Request mode. Default: rcube_utils::INPUT_GPC.
*
* @return array List of message UIDs per folder
*/
public static function get_uids($uids = null, $mbox = null, &$is_multifolder = false, $mode = null)
{
// message UID (or comma-separated list of IDs) is provided in
// the form of <ID>-<MBOX>[,<ID>-<MBOX>]*
$_uid = $uids ?: rcube_utils::get_input_value('_uid', $mode ?: rcube_utils::INPUT_GPC);
$_mbox = $mbox ?: (string) rcube_utils::get_input_value('_mbox', $mode ?: rcube_utils::INPUT_GPC);
// already a hash array
if (is_array($_uid) && !isset($_uid[0])) {
return $_uid;
}
$result = [];
// special case: *
- if ($_uid == '*' && is_object($_SESSION['search'][1]) && $_SESSION['search'][1]->multi) {
+ if ($_uid == '*' && !empty($_SESSION['search'][1]) && !empty($_SESSION['search'][1]->multi)) {
$is_multifolder = true;
// extract the full list of UIDs per folder from the search set
foreach ($_SESSION['search'][1]->sets as $subset) {
$mbox = $subset->get_parameters('MAILBOX');
$result[$mbox] = $subset->get();
}
}
else {
if (is_string($_uid)) {
$_uid = explode(',', $_uid);
}
// create a per-folder UIDs array
foreach ((array) $_uid as $uid) {
$tokens = explode('-', $uid, 2);
$uid = $tokens[0];
if (!isset($tokens[1]) || !strlen($tokens[1])) {
$mbox = $_mbox;
}
else {
$mbox = $tokens[1];
$is_multifolder = true;
}
if ($uid == '*') {
$result[$mbox] = $uid;
}
else if (preg_match('/^[0-9:.]+$/', $uid)) {
$result[$mbox][] = $uid;
}
}
}
return $result;
}
/**
* Get resource file content (with assets_dir support)
*
* @param string $name File name
*
* @return string File content
*/
public static function get_resource_content($name)
{
if (strpos($name, '/') !== 0) {
$name = "program/resources/$name";
}
$assets_dir = rcmail::get_instance()->config->get('assets_dir');
if ($assets_dir) {
$path = slashify($assets_dir) . $name;
if (@file_exists($path)) {
$name = $path;
}
}
else {
$name = INSTALL_PATH . $name;
}
return file_get_contents($name, false);
}
/**
* Prepare a common edit form.
*
* @param array $attrib Form attributes
* @param string $action Action name
* @param string $id An extra index for the form key
* @param array $hidden Additional hidden fields
*
* @return array Start and end tags, Empty if the farm was initialized before
*/
public static function get_form_tags($attrib, $action, $id = null, $hidden = [])
{
$rcmail = rcmail::get_instance();
$form_start = $form_end = '';
if (empty(self::$edit_form)) {
$request_key = $action . (isset($id) ? '.'.$id : '');
$form_start = $rcmail->output->request_form([
'name' => 'form',
'method' => 'post',
'task' => $rcmail->task,
'action' => $action,
'request' => $request_key,
'noclose' => true
] + $attrib
);
if (!empty($hidden) && is_array($hidden)) {
$hiddenfields = new html_hiddenfield($hidden);
$form_start .= $hiddenfields->show();
}
$form_end = empty($attrib['form']) ? '</form>' : '';
self::$edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form';
$rcmail->output->add_gui_object('editform', self::$edit_form);
}
return [$form_start, $form_end];
}
/**
* Return folders list in HTML
*
* @param array $attrib Named parameters
*
* @return string HTML code for the gui object
*/
public static function folder_list($attrib)
{
static $a_mailboxes;
$attrib += ['maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)'];
$type = !empty($attrib['type']) ? $attrib['type'] : 'ul';
unset($attrib['type']);
if ($type == 'ul' && empty($attrib['id'])) {
$attrib['id'] = 'rcmboxlist';
}
if (empty($attrib['folder_name'])) {
$attrib['folder_name'] = '*';
}
// get current folder
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox_name = $storage->get_folder();
$delimiter = $storage->get_hierarchy_delimiter();
// build the folders tree
if (empty($a_mailboxes)) {
// get mailbox list
$a_mailboxes = [];
$a_folders = $storage->list_folders_subscribed(
'',
$attrib['folder_name'],
isset($attrib['folder_filter']) ? $attrib['folder_filter'] : null
);
foreach ($a_folders as $folder) {
self::build_folder_tree($a_mailboxes, $folder, $delimiter);
}
}
// allow plugins to alter the folder tree or to localize folder names
$hook = $rcmail->plugins->exec_hook('render_mailboxlist', [
'list' => $a_mailboxes,
'delimiter' => $delimiter,
'type' => $type,
'attribs' => $attrib,
]);
$a_mailboxes = $hook['list'];
$attrib = $hook['attribs'];
if ($type == 'select') {
$attrib['is_escaped'] = true;
$select = new html_select($attrib);
// add no-selection option
if (!empty($attrib['noselection'])) {
$select->add(html::quote($rcmail->gettext($attrib['noselection'])), '');
}
$maxlength = isset($attrib['maxlength']) ? $attrib['maxlength'] : null;
$realnames = isset($attrib['realnames']) ? $attrib['realnames'] : null;
$default = isset($attrib['default']) ? $attrib['default'] : null;
self::render_folder_tree_select($a_mailboxes, $mbox_name, $maxlength, $select, $realnames);
$out = $select->show($default);
}
else {
$out = '';
$js_mailboxlist = [];
$tree = self::render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib);
if ($type != 'js') {
$out = html::tag('ul', $attrib, $tree, html::$common_attrib);
$rcmail->output->include_script('treelist.js');
$rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
$rcmail->output->set_env('unreadwrap', isset($attrib['unreadwrap']) ? $attrib['unreadwrap'] : false);
$rcmail->output->set_env('collapsed_folders', (string) $rcmail->config->get('collapsed_folders'));
}
$rcmail->output->set_env('mailboxes', $js_mailboxlist);
// we can't use object keys in javascript because they are unordered
// we need sorted folders list for folder-selector widget
$rcmail->output->set_env('mailboxes_list', array_keys($js_mailboxlist));
}
// add some labels to client
$rcmail->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
return $out;
}
/**
* Return folders list as html_select object
*
* @param array $p Named parameters
*
* @return html_select HTML drop-down object
*/
public static function folder_selector($p = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$realnames = $rcmail->config->get('show_real_foldernames');
$p += ['maxlength' => 100, 'realnames' => $realnames, 'is_escaped' => true];
$a_mailboxes = [];
if (empty($p['folder_name'])) {
$p['folder_name'] = '*';
}
$f_filter = isset($p['folder_filter']) ? $p['folder_filter'] : null;
$f_rights = isset($p['folder_rights']) ? $p['folder_rights'] : null;
if (!empty($p['unsubscribed'])) {
$list = $storage->list_folders('', $p['folder_name'], $f_filter, $f_rights);
}
else {
$list = $storage->list_folders_subscribed('', $p['folder_name'], $f_filter, $f_rights);
}
$delimiter = $storage->get_hierarchy_delimiter();
if (!empty($p['exceptions'])) {
$list = array_diff($list, (array) $p['exceptions']);
}
if (!empty($p['additional'])) {
foreach ($p['additional'] as $add_folder) {
$add_items = explode($delimiter, $add_folder);
$folder = '';
while (count($add_items)) {
$folder .= array_shift($add_items);
// @TODO: sorting
if (!in_array($folder, $list)) {
$list[] = $folder;
}
$folder .= $delimiter;
}
}
}
foreach ($list as $folder) {
self::build_folder_tree($a_mailboxes, $folder, $delimiter);
}
// allow plugins to alter the folder tree or to localize folder names
$hook = $rcmail->plugins->exec_hook('render_folder_selector', [
'list' => $a_mailboxes,
'delimiter' => $delimiter,
'attribs' => $p,
]);
$a_mailboxes = $hook['list'];
$p = $hook['attribs'];
$select = new html_select($p);
if (!empty($p['noselection'])) {
$select->add(html::quote($p['noselection']), '');
}
self::render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
return $select;
}
/**
* Create a hierarchical array of the mailbox list
*/
protected static function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
// Handle namespace prefix
$prefix = '';
if (!$path) {
$n_folder = $folder;
$folder = $storage->mod_folder($folder);
if ($n_folder != $folder) {
$prefix = substr($n_folder, 0, -strlen($folder));
}
}
$pos = strpos($folder, $delm);
if ($pos !== false) {
$subFolders = substr($folder, $pos+1);
$currentFolder = substr($folder, 0, $pos);
// sometimes folder has a delimiter as the last character
if (!strlen($subFolders)) {
$virtual = false;
}
else if (!isset($arrFolders[$currentFolder])) {
$virtual = true;
}
else {
$virtual = $arrFolders[$currentFolder]['virtual'];
}
}
else {
$subFolders = false;
$currentFolder = $folder;
$virtual = false;
}
$path .= $prefix . $currentFolder;
if (!isset($arrFolders[$currentFolder])) {
$arrFolders[$currentFolder] = [
'id' => $path,
'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
'virtual' => $virtual,
'folders' => []
];
}
else {
$arrFolders[$currentFolder]['virtual'] = $virtual;
}
if (strlen($subFolders)) {
self::build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
}
/**
* Return html for a structured list <ul> for the mailbox tree
*/
protected static function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $storage->get_cache('messagecount');
$collapsed = $rcmail->config->get('collapsed_folders');
$realnames = $rcmail->config->get('show_real_foldernames');
$out = '';
foreach ($arrFolders as $folder) {
$title = null;
$folder_class = self::folder_classname($folder['id']);
$is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
$unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
if ($folder_class && !$realnames && $rcmail->text_exists($folder_class)) {
$foldername = $rcmail->gettext($folder_class);
}
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength > 1) {
$fname = abbreviate_string($foldername, $maxlength);
if ($fname != $foldername) {
$title = $foldername;
}
$foldername = $fname;
}
}
// make folder name safe for ids and class names
$folder_id = rcube_utils::html_identifier($folder['id'], true);
$classes = ['mailbox'];
// set special class for Sent, Drafts, Trash and Junk
if ($folder_class) {
$classes[] = $folder_class;
}
if ($folder['id'] == $mbox_name) {
$classes[] = 'selected';
}
if ($folder['virtual']) {
$classes[] = 'virtual';
}
else if ($unread) {
$classes[] = 'unread';
}
$js_name = rcube::JQ($folder['id']);
$html_name = rcube::Q($foldername) . ($unread ? html::span('unreadcount skip-content', sprintf($attrib['unreadwrap'], $unread)) : '');
$link_attrib = $folder['virtual'] ? [] : [
'href' => $rcmail->url(['_mbox' => $folder['id']]),
'onclick' => sprintf("return %s.command('list','%s',this,event)", rcmail_output::JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
];
$out .= html::tag('li', [
'id' => "rcmli" . $folder_id,
'class' => implode(' ', $classes),
'noclose' => true
],
html::a($link_attrib, $html_name)
);
if (!empty($folder['folders'])) {
$out .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' ');
}
$jslist[$folder['id']] = [
'id' => $folder['id'],
'name' => $foldername,
'virtual' => $folder['virtual'],
];
if (!empty($folder_class)) {
$jslist[$folder['id']]['class'] = $folder_class;
}
if (!empty($folder['folders'])) {
$out .= html::tag('ul', ['style' => $is_collapsed ? "display:none;" : null],
self::render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
}
$out .= "</li>\n";
}
return $out;
}
/**
* Return html for a flat list <select> for the mailbox tree
*/
protected static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = [])
{
$out = '';
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
foreach ($arrFolders as $folder) {
// skip exceptions (and its subfolders)
if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
continue;
}
// skip folders in which it isn't possible to create subfolders
if (!empty($opts['skip_noinferiors'])) {
$attrs = $storage->folder_attributes($folder['id']);
if ($attrs && in_array_nocase('\\Noinferiors', $attrs)) {
continue;
}
}
if (!$realnames && ($folder_class = self::folder_classname($folder['id'])) && $rcmail->text_exists($folder_class)) {
$foldername = $rcmail->gettext($folder_class);
}
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength > 1) {
$foldername = abbreviate_string($foldername, $maxlength);
}
}
$select->add(str_repeat(' ', $nestLevel*4) . html::quote($foldername), $folder['id']);
if (!empty($folder['folders'])) {
$out .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
$select, $realnames, $nestLevel+1, $opts);
}
}
return $out;
}
/**
* Returns class name for the given folder if it is a special folder
* (including shared/other users namespace roots).
*
* @param string $folder_id IMAP Folder name
*
* @return string|null CSS class name
*/
public static function folder_classname($folder_id)
{
static $classes;
if ($classes === null) {
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$classes = ['INBOX' => 'inbox'];
// for these mailboxes we have css classes
foreach (['sent', 'drafts', 'trash', 'junk'] as $type) {
if (($mbox = $rcmail->config->get($type . '_mbox')) && !isset($classes[$mbox])) {
$classes[$mbox] = $type;
}
}
// add classes for shared/other user namespace roots
foreach (['other', 'shared'] as $ns_name) {
if ($ns = $storage->get_namespace($ns_name)) {
foreach ($ns as $root) {
$root = substr($root[0], 0, -1);
if (strlen($root) && !isset($classes[$root])) {
$classes[$root] = "ns-$ns_name";
}
}
}
}
}
return !empty($classes[$folder_id]) ? $classes[$folder_id] : null;
}
/**
* Try to localize the given IMAP folder name.
* UTF-7 decode it in case no localized text was found
*
* @param string $name Folder name
* @param bool $with_path Enable path localization
* @param bool $path_remove Remove the path
*
* @return string Localized folder name in UTF-8 encoding
*/
public static function localize_foldername($name, $with_path = false, $path_remove = false)
{
$rcmail = rcmail::get_instance();
$realnames = $rcmail->config->get('show_real_foldernames');
if (!$realnames && ($folder_class = self::folder_classname($name)) && $rcmail->text_exists($folder_class)) {
return $rcmail->gettext($folder_class);
}
$storage = $rcmail->get_storage();
$delimiter = $storage->get_hierarchy_delimiter();
// Remove the path
if ($path_remove) {
if (strpos($name, $delimiter)) {
$path = explode($delimiter, $name);
$name = array_pop($path);
}
}
// try to localize path of the folder
else if ($with_path && !$realnames) {
$path = explode($delimiter, $name);
$count = count($path);
if ($count > 1) {
for ($i = 1; $i < $count; $i++) {
$folder = implode($delimiter, array_slice($path, 0, -$i));
$folder_class = self::folder_classname($folder);
if ($folder_class && $rcmail->text_exists($folder_class)) {
$name = implode($delimiter, array_slice($path, $count - $i));
$name = rcube_charset::convert($name, 'UTF7-IMAP');
return $rcmail->gettext($folder_class) . $delimiter . $name;
}
}
}
}
return rcube_charset::convert($name, 'UTF7-IMAP');
}
/**
* Localize folder path
*/
public static function localize_folderpath($path)
{
$rcmail = rcmail::get_instance();
$protect_folders = $rcmail->config->get('protect_default_folders');
$delimiter = $rcmail->storage->get_hierarchy_delimiter();
$path = explode($delimiter, $path);
$result = [];
foreach ($path as $idx => $dir) {
$directory = implode($delimiter, array_slice($path, 0, $idx+1));
if ($protect_folders && $rcmail->storage->is_special_folder($directory)) {
unset($result);
$result[] = self::localize_foldername($directory);
}
else {
$result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
}
}
return implode($delimiter, $result);
}
/**
* Gets a value of a boolean attribute from template object attributes
*
* @param array $attributes Template object attributes
* @param string $name Attribute name
*/
public static function get_bool_attr($attributes, $name)
{
if (!isset($attributes[$name])) {
return false;
}
return rcube_utils::get_boolean($attributes[$name]);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Dec 18, 12:40 PM (2 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
418766
Default Alt Text
(125 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment