Import Ruty
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
<?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: |
|
||||
| Add the submitted contact to the user's address book |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_addcontact extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$address = rcube_utils::get_input_string('_address', rcube_utils::INPUT_POST, true);
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_POST);
|
||||
|
||||
// Get the default addressbook
|
||||
$CONTACTS = null;
|
||||
$SENDERS = null;
|
||||
$type = 0;
|
||||
|
||||
if ($source != rcube_addressbook::TYPE_TRUSTED_SENDER) {
|
||||
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
|
||||
$type = rcube_addressbook::TYPE_DEFAULT;
|
||||
}
|
||||
|
||||
// Get the trusted senders addressbook
|
||||
if (!empty($_POST['_reload']) || $source == rcube_addressbook::TYPE_TRUSTED_SENDER) {
|
||||
$collected_senders = $rcmail->config->get('collected_senders');
|
||||
|
||||
if (strlen($collected_senders)) {
|
||||
$type |= rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
$SENDERS = $rcmail->get_address_book($collected_senders);
|
||||
if ($CONTACTS == $SENDERS) {
|
||||
$SENDERS = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contact = rcube_mime::decode_address_list($address, 1, false);
|
||||
|
||||
if (empty($contact[1]['mailto'])) {
|
||||
$rcmail->output->show_message('errorsavingcontact', 'error', null, false);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$contact = [
|
||||
'email' => $contact[1]['mailto'],
|
||||
'name' => $contact[1]['name'],
|
||||
];
|
||||
|
||||
$email = rcube_utils::idn_to_ascii($contact['email']);
|
||||
|
||||
if (!rcube_utils::check_email($email, false)) {
|
||||
$rcmail->output->show_message('emailformaterror', 'error', ['email' => $contact['email']], false);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if ($rcmail->contact_exists($contact['email'], $type)) {
|
||||
$rcmail->output->show_message('contactexists', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$done = $rcmail->contact_create($contact, $SENDERS ?: $CONTACTS, $error);
|
||||
|
||||
if ($done) {
|
||||
$rcmail->output->show_message('addedsuccessfully', 'confirmation');
|
||||
|
||||
if (!empty($_POST['_reload'])) {
|
||||
$rcmail->output->command('command', 'load-remote');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message($error ?: 'errorsavingcontact', 'error', null, false);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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: |
|
||||
| Delete attachments from compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_delete extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
self::init();
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$attachment = self::get_attachment();
|
||||
|
||||
if (is_array($attachment)) {
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_delete', $attachment);
|
||||
|
||||
if (!empty($attachment['status'])) {
|
||||
$rcmail->session->remove(self::$SESSION_KEY . '.attachments.' . self::$file_id);
|
||||
$rcmail->output->command('remove_from_attachment_list', 'rcmfile' . self::$file_id);
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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: |
|
||||
| Display attachments in compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
self::init();
|
||||
self::display_uploaded_file(self::get_attachment());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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: |
|
||||
| Rename attachments in compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_rename extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
self::init();
|
||||
|
||||
$filename = rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST);
|
||||
$filename = trim($filename);
|
||||
|
||||
if (
|
||||
strlen($filename)
|
||||
&& ($attachment = self::get_attachment())
|
||||
&& is_array($attachment)
|
||||
) {
|
||||
$attachment['name'] = $filename;
|
||||
|
||||
$rcmail->session->remove(self::$SESSION_KEY . '.attachments. ' . self::$file_id);
|
||||
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
|
||||
|
||||
$rcmail->output->command('rename_attachment_handler', 'rcmfile' . self::$file_id, $filename);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?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: |
|
||||
| Attachment uploads handler for the compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_upload extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
protected static $SESSION_KEY;
|
||||
protected static $COMPOSE;
|
||||
protected static $COMPOSE_ID;
|
||||
protected static $file_id;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
self::init();
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
$uploadid = rcube_utils::get_input_string('_uploadid', rcube_utils::INPUT_GPC);
|
||||
$uri = rcube_utils::get_input_string('_uri', rcube_utils::INPUT_POST);
|
||||
|
||||
// handle dropping a reference to an attachment part of some message
|
||||
if ($uri) {
|
||||
$attachment = null;
|
||||
|
||||
$url = parse_url($uri);
|
||||
|
||||
if (!empty($url['query'])) {
|
||||
parse_str($url['query'], $params);
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($params) && isset($params['_mbox']) && strlen($params['_mbox'])
|
||||
&& !empty($params['_uid']) && !empty($params['_part'])
|
||||
) {
|
||||
// @TODO: at some point we might support drag-n-drop between
|
||||
// two different accounts on the same server, for now make sure
|
||||
// this is the same server and the same user
|
||||
list($host, $port) = rcube_utils::explode(':', $_SERVER['HTTP_HOST']);
|
||||
|
||||
if (
|
||||
$host == $url['host']
|
||||
&& $port == $url['port']
|
||||
&& $rcmail->get_user_name() == rawurldecode($url['user'])
|
||||
) {
|
||||
$message = new rcube_message($params['_uid'], $params['_mbox']);
|
||||
|
||||
if ($message && !empty($message->headers)) {
|
||||
$attachment = rcmail_action_mail_compose::save_attachment($message, $params['_part'], self::$COMPOSE_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('attachment_from_uri', [
|
||||
'attachment' => $attachment,
|
||||
'uri' => $uri,
|
||||
'compose_id' => self::$COMPOSE_ID
|
||||
]);
|
||||
|
||||
if ($plugin['attachment']) {
|
||||
self::attachment_success($plugin['attachment'], $uploadid);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('display_message', $rcmail->gettext('filelinkerror'), 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
// handle file(s) upload
|
||||
if (is_array($_FILES['_attachments']['tmp_name'])) {
|
||||
$multiple = count($_FILES['_attachments']['tmp_name']) > 1;
|
||||
$errors = [];
|
||||
|
||||
foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded attachment if there is no error
|
||||
$err = $_FILES['_attachments']['error'][$i];
|
||||
|
||||
if (!$err) {
|
||||
$filename = $_FILES['_attachments']['name'][$i];
|
||||
$filesize = $_FILES['_attachments']['size'][$i];
|
||||
$filetype = rcube_mime::file_content_type($filepath, $filename, $_FILES['_attachments']['type'][$i]);
|
||||
|
||||
if ($err = self::check_message_size($filesize, $filetype)) {
|
||||
if (!in_array($err, $errors)) {
|
||||
$rcmail->output->command('display_message', $err, 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
$errors[] = $err;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_upload', [
|
||||
'path' => $filepath,
|
||||
'name' => $filename,
|
||||
'size' => $filesize,
|
||||
'mimetype' => $filetype,
|
||||
'group' => self::$COMPOSE_ID,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$err && !empty($attachment['status']) && empty($attachment['abort'])) {
|
||||
// store new attachment in session
|
||||
unset($attachment['status'], $attachment['abort']);
|
||||
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
|
||||
|
||||
self::attachment_success($attachment, $uploadid);
|
||||
}
|
||||
else { // upload failed
|
||||
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
|
||||
$size = self::show_bytes(rcube_utils::max_upload_size());
|
||||
$msg = $rcmail->gettext(['name' => 'filesizeerror', 'vars' => ['size' => $size]]);
|
||||
}
|
||||
else if (!empty($attachment['error'])) {
|
||||
$msg = $attachment['error'];
|
||||
}
|
||||
else {
|
||||
$msg = $rcmail->gettext('fileuploaderror');
|
||||
}
|
||||
|
||||
if (!empty($attachment['error']) || $err != UPLOAD_ERR_NO_FILE) {
|
||||
if (!in_array($msg, $errors)) {
|
||||
$rcmail->output->command('display_message', $msg, 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
$errors[] = $msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (self::upload_failure()) {
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
// send html page with JS calls as response
|
||||
$rcmail->output->command('auto_save_start', false);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
|
||||
self::$COMPOSE = null;
|
||||
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;
|
||||
|
||||
if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
|
||||
self::$COMPOSE =& $_SESSION[self::$SESSION_KEY];
|
||||
}
|
||||
|
||||
if (!self::$COMPOSE) {
|
||||
die("Invalid session var!");
|
||||
}
|
||||
|
||||
self::$file_id = rcube_utils::get_input_string('_file', rcube_utils::INPUT_GPC);
|
||||
self::$file_id = preg_replace('/^rcmfile/', '', self::$file_id) ?: 'unknown';
|
||||
}
|
||||
|
||||
public static function get_attachment()
|
||||
{
|
||||
return self::$COMPOSE['attachments'][self::$file_id];
|
||||
}
|
||||
|
||||
public static function attachment_success($attachment, $uploadid)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$id = $attachment['id'];
|
||||
|
||||
if (!empty(self::$COMPOSE['deleteicon']) && is_file(self::$COMPOSE['deleteicon'])) {
|
||||
$button = html::img([
|
||||
'src' => self::$COMPOSE['deleteicon'],
|
||||
'alt' => $rcmail->gettext('delete')
|
||||
]);
|
||||
}
|
||||
else if (!empty(self::$COMPOSE['textbuttons'])) {
|
||||
$button = rcube::Q($rcmail->gettext('delete'));
|
||||
}
|
||||
else {
|
||||
$button = '';
|
||||
}
|
||||
|
||||
$link_content = sprintf(
|
||||
'<span class="attachment-name">%s</span><span class="attachment-size">(%s)</span>',
|
||||
rcube::Q($attachment['name']), self::show_bytes($attachment['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
|
||||
),
|
||||
], $link_content);
|
||||
|
||||
$delete_link = html::a([
|
||||
'href' => "#delete",
|
||||
'onclick' => sprintf(
|
||||
"return %s.command('remove-attachment','rcmfile%s', this, event)",
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
$id
|
||||
),
|
||||
'title' => $rcmail->gettext('delete'),
|
||||
'class' => 'delete',
|
||||
'aria-label' => $rcmail->gettext('delete') . ' ' . $attachment['name'],
|
||||
], $button);
|
||||
|
||||
if (!empty(self::$COMPOSE['icon_pos']) && self::$COMPOSE['icon_pos'] == 'left') {
|
||||
$content = $delete_link . $content_link;
|
||||
}
|
||||
else {
|
||||
$content = $content_link . $delete_link;
|
||||
}
|
||||
|
||||
$rcmail->output->command('add2attachment_list', "rcmfile$id", [
|
||||
'html' => $content,
|
||||
'name' => $attachment['name'],
|
||||
'mimetype' => $attachment['mimetype'],
|
||||
'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']),
|
||||
'complete' => true
|
||||
],
|
||||
$uploadid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the attached file will fit in message size limit.
|
||||
* Calculates size of all attachments and compares with the limit.
|
||||
*
|
||||
* @param int $filesize File size
|
||||
* @param string $filetype File mimetype
|
||||
*
|
||||
* @return string Error message if the limit is exceeded
|
||||
*/
|
||||
public static function check_message_size($filesize, $filetype)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$limit = parse_bytes($rcmail->config->get('max_message_size'));
|
||||
$size = 10 * 1024; // size of message body
|
||||
|
||||
if (!$limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add size of already attached files
|
||||
if (!empty(self::$COMPOSE['attachments'])) {
|
||||
foreach ((array) self::$COMPOSE['attachments'] as $att) {
|
||||
// All attachments are base64-encoded except message/rfc822 (see sendmail.inc)
|
||||
$multip = $att['mimetype'] == 'message/rfc822' ? 1 : 1.33;
|
||||
$size += $att['size'] * $multip;
|
||||
}
|
||||
}
|
||||
|
||||
// add size of the new attachment
|
||||
$multip = $filetype == 'message/rfc822' ? 1 : 1.33;
|
||||
$size += $filesize * $multip;
|
||||
|
||||
if ($size > $limit) {
|
||||
$limit = self::show_bytes($limit);
|
||||
return $rcmail->gettext(['name' => 'msgsizeerror', 'vars' => ['size' => $limit]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?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: |
|
||||
| Perform a search on configured address books for the email |
|
||||
| address autocompletion |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_autocomplete extends rcmail_action
|
||||
{
|
||||
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();
|
||||
$MAXNUM = (int) $rcmail->config->get('autocomplete_max', 15);
|
||||
$mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$single = (bool) $rcmail->config->get('autocomplete_single');
|
||||
$search = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC, true);
|
||||
$reqid = rcube_utils::get_input_string('_reqid', rcube_utils::INPUT_GPC);
|
||||
|
||||
$contacts = [];
|
||||
|
||||
if (strlen($search) && ($book_types = self::autocomplete_addressbooks())) {
|
||||
$sort_keys = [];
|
||||
$books_num = count($book_types);
|
||||
$search_lc = mb_strtolower($search);
|
||||
$mode |= rcube_addressbook::SEARCH_GROUPS;
|
||||
$fields = $rcmail->config->get('contactlist_fields');
|
||||
|
||||
foreach ($book_types as $abook_id) {
|
||||
$abook = $rcmail->get_address_book($abook_id);
|
||||
$abook->set_pagesize($MAXNUM);
|
||||
|
||||
if ($result = $abook->search($fields, $search, $mode, true, true, 'email')) {
|
||||
while ($record = $result->iterate()) {
|
||||
// Contact can have more than one e-mail address
|
||||
$email_arr = (array) $abook->get_col_values('email', $record, true);
|
||||
$email_cnt = count($email_arr);
|
||||
$idx = 0;
|
||||
|
||||
foreach ($email_arr as $email) {
|
||||
if (empty($email)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = rcube_addressbook::compose_list_name($record);
|
||||
$contact = format_email_recipient($email, $name);
|
||||
|
||||
// skip entries that don't match
|
||||
if ($email_cnt > 1 && strpos(mb_strtolower($contact), $search_lc) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$index = $contact;
|
||||
|
||||
// skip duplicates
|
||||
if (empty($contacts[$index])) {
|
||||
$contact = [
|
||||
'name' => $contact,
|
||||
'type' => $record['_type'] ?? null,
|
||||
'id' => $record['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
$display = rcube_addressbook::compose_search_name($record, $email, $name);
|
||||
|
||||
if ($display && $display != $contact['name']) {
|
||||
$contact['display'] = $display;
|
||||
}
|
||||
|
||||
// groups with defined email address will not be expanded to its members' addresses
|
||||
if ($contact['type'] == 'group') {
|
||||
$contact['email'] = $email;
|
||||
}
|
||||
|
||||
$name = !empty($contact['display']) ? $contact['display'] : $name;
|
||||
$contacts[$index] = $contact;
|
||||
$sort_keys[$index] = sprintf('%s %03d', $name, $idx++);
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
// skip redundant entries (show only first email address)
|
||||
if ($single) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also list matching contact groups
|
||||
if ($abook->groups && count($contacts) < $MAXNUM) {
|
||||
foreach ($abook->list_groups($search, $mode) as $group) {
|
||||
$abook->reset();
|
||||
$abook->set_group($group['ID']);
|
||||
|
||||
$group_prop = $abook->get_group($group['ID']);
|
||||
|
||||
// group (distribution list) with email address(es)
|
||||
if (!empty($group_prop['email'])) {
|
||||
$idx = 0;
|
||||
foreach ((array) $group_prop['email'] as $email) {
|
||||
$index = format_email_recipient($email, $group['name']);
|
||||
|
||||
if (empty($contacts[$index])) {
|
||||
$sort_keys[$index] = sprintf('%s %03d', $group['name'] , $idx++);
|
||||
$contacts[$index] = [
|
||||
'name' => $index,
|
||||
'email' => $email,
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// show group with count
|
||||
else if (($result = $abook->count()) && $result->count) {
|
||||
if (empty($contacts[$group['name']])) {
|
||||
$sort_keys[$group['name']] = $group['name'];
|
||||
$contacts[$group['name']] = [
|
||||
'name' => $group['name'] . ' (' . intval($result->count) . ')',
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($contacts)) {
|
||||
// sort contacts index
|
||||
asort($sort_keys, SORT_LOCALE_STRING);
|
||||
// re-sort contacts according to index
|
||||
foreach ($sort_keys as $idx => $val) {
|
||||
$sort_keys[$idx] = $contacts[$idx];
|
||||
}
|
||||
$contacts = array_values($sort_keys);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow autocomplete result optimization via plugin
|
||||
$plugin = $rcmail->plugins->exec_hook('contacts_autocomplete_after', [
|
||||
'search' => $search,
|
||||
// Provide already-found contacts to plugin if they are required
|
||||
'contacts' => $contacts,
|
||||
]);
|
||||
|
||||
$contacts = $plugin['contacts'];
|
||||
|
||||
$rcmail->output->command('ksearch_query_results', $contacts, $search, $reqid);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect addressbook sources used for autocompletion
|
||||
*/
|
||||
public static function autocomplete_addressbooks()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
|
||||
if (strlen($source)) {
|
||||
$book_types = [$source];
|
||||
}
|
||||
else {
|
||||
$book_types = (array) $rcmail->config->get('autocomplete_addressbooks', 'sql');
|
||||
}
|
||||
|
||||
$collected_recipients = $rcmail->config->get('collected_recipients');
|
||||
$collected_senders = $rcmail->config->get('collected_senders');
|
||||
|
||||
if (strlen($collected_recipients) && !in_array($collected_recipients, $book_types)) {
|
||||
$book_types[] = $collected_recipients;
|
||||
}
|
||||
|
||||
if (strlen($collected_senders) && !in_array($collected_senders, $book_types)) {
|
||||
$book_types[] = $collected_senders;
|
||||
}
|
||||
|
||||
return !empty($book_types) ? $book_types : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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: |
|
||||
| Bounce/resend an email message |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_bounce extends rcmail_action
|
||||
{
|
||||
protected static $MESSAGE;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$msg_uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
|
||||
$msg_folder = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GP, true);
|
||||
$MESSAGE = new rcube_message($msg_uid, $msg_folder);
|
||||
|
||||
self::$MESSAGE = $MESSAGE;
|
||||
|
||||
if (!$MESSAGE->headers) {
|
||||
$rcmail->output->show_message('messageopenerror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
// Display Bounce form
|
||||
if (empty($_POST)) {
|
||||
if (!empty($MESSAGE->headers->charset)) {
|
||||
$rcmail->storage->set_charset($MESSAGE->headers->charset);
|
||||
}
|
||||
|
||||
// Initialize helper class to build the UI
|
||||
$SENDMAIL = new rcmail_sendmail(
|
||||
['mode' => rcmail_sendmail::MODE_FORWARD],
|
||||
['message' => $MESSAGE]
|
||||
);
|
||||
|
||||
$rcmail->output->set_env('mailbox', $msg_folder);
|
||||
$rcmail->output->set_env('uid', $msg_uid);
|
||||
$rcmail->output->add_handler('bounceobjects', [$this, 'bounce_objects']);
|
||||
$rcmail->output->send('bounce');
|
||||
}
|
||||
|
||||
// Initialize helper class to send the message
|
||||
$SENDMAIL = new rcmail_sendmail(
|
||||
['mode' => rcmail_sendmail::MODE_FORWARD],
|
||||
[
|
||||
'sendmail' => true,
|
||||
'error_handler' => function(...$args) use ($rcmail) {
|
||||
call_user_func_array([$rcmail->output, 'show_message'], $args);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Handle the form input
|
||||
$input_headers = $SENDMAIL->headers_input();
|
||||
|
||||
// Set Resent-* headers, these will be added on top of the bounced message
|
||||
$headers = [];
|
||||
foreach (['From', 'To', 'Cc', 'Bcc', 'Date', 'Message-ID'] as $name) {
|
||||
if (!empty($input_headers[$name])) {
|
||||
$headers['Resent-' . $name] = $input_headers[$name];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the bounce message
|
||||
$BOUNCE = new rcmail_resend_mail([
|
||||
'bounce_message' => $MESSAGE,
|
||||
'bounce_headers' => $headers,
|
||||
]);
|
||||
|
||||
// Send the bounce message
|
||||
$SENDMAIL->deliver_message($BOUNCE);
|
||||
|
||||
// Save in Sent (if requested)
|
||||
$saved = $SENDMAIL->save_message($BOUNCE);
|
||||
|
||||
if (!$saved && strlen($SENDMAIL->options['store_target'])) {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('messagesent', 'confirmation', null, false);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for template object 'bounceObjects'
|
||||
*
|
||||
* @param array $attrib HTML attributes
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
public static function bounce_objects($attrib)
|
||||
{
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'bounce-objects';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$content = [];
|
||||
|
||||
// Always display a hint about the bounce feature behavior
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('bouncehint')));
|
||||
$msg_attrib = ['id' => 'bounce-hint', 'class' => 'boxinformation'];
|
||||
$content[] = html::div($msg_attrib, $msg);
|
||||
|
||||
// Add a warning about Bcc recipients
|
||||
if (self::$MESSAGE->headers->get('bcc', false) || self::$MESSAGE->headers->get('resent-bcc', false)) {
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('bccemail')));
|
||||
$msg_attrib = ['id' => 'bcc-warning', 'class' => 'boxwarning'];
|
||||
$content[] = html::div($msg_attrib, $msg);
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('bounce_objects',
|
||||
['content' => $content, 'message' => self::$MESSAGE]);
|
||||
|
||||
$content = implode("\n", $plugin['content']);
|
||||
|
||||
return $content ? html::div($attrib, $content) : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?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: |
|
||||
| Check for recent messages, in all mailboxes |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_check_recent extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// If there's no folder or messages list, there's nothing to update
|
||||
// This can happen on 'refresh' request
|
||||
if (empty($_POST['_folderlist']) && empty($_POST['_list'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$current = $rcmail->storage->get_folder();
|
||||
$check_all = $rcmail->action != 'refresh' || (bool) $rcmail->config->get('check_all_folders');
|
||||
$page = $rcmail->storage->get_page();
|
||||
$page_size = $rcmail->storage->get_pagesize();
|
||||
|
||||
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
|
||||
if ($search_request && $_SESSION['search_request'] != $search_request) {
|
||||
$search_request = null;
|
||||
}
|
||||
|
||||
// list of folders to check
|
||||
if ($check_all) {
|
||||
$a_mailboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
}
|
||||
else if ($search_request && isset($_SESSION['search'][1]) && is_object($_SESSION['search'][1])) {
|
||||
$a_mailboxes = (array) $_SESSION['search'][1]->get_parameters('MAILBOX');
|
||||
}
|
||||
else {
|
||||
$a_mailboxes = (array) $current;
|
||||
if ($current != 'INBOX') {
|
||||
$a_mailboxes[] = 'INBOX';
|
||||
}
|
||||
}
|
||||
|
||||
// Control folders list from a plugin
|
||||
$plugin = $rcmail->plugins->exec_hook('check_recent', ['folders' => $a_mailboxes, 'all' => $check_all]);
|
||||
$a_mailboxes = $plugin['folders'];
|
||||
$list_cleared = false;
|
||||
|
||||
self::storage_fatal_error();
|
||||
|
||||
// check recent/unseen counts
|
||||
foreach ($a_mailboxes as $mbox_name) {
|
||||
$is_current = $mbox_name == $current
|
||||
|| (
|
||||
!empty($search_request)
|
||||
&& isset($_SESSION['search'][1])
|
||||
&& is_object($_SESSION['search'][1])
|
||||
&& in_array($mbox_name, (array)$_SESSION['search'][1]->get_parameters('MAILBOX'))
|
||||
);
|
||||
|
||||
if ($is_current) {
|
||||
// Synchronize mailbox cache, handle flag changes
|
||||
$rcmail->storage->folder_sync($mbox_name);
|
||||
}
|
||||
|
||||
// Get mailbox status
|
||||
$status = $rcmail->storage->folder_status($mbox_name, $diff);
|
||||
|
||||
if ($is_current) {
|
||||
self::storage_fatal_error();
|
||||
}
|
||||
|
||||
if ($status & 1) {
|
||||
// trigger plugin hook
|
||||
$rcmail->plugins->exec_hook('new_messages', [
|
||||
'mailbox' => $mbox_name,
|
||||
'is_current' => $is_current,
|
||||
'diff' => $diff
|
||||
]);
|
||||
}
|
||||
|
||||
self::send_unread_count($mbox_name, true, null, (!$is_current && ($status & 1)) ? 'recent' : '');
|
||||
|
||||
if ($status && $is_current) {
|
||||
// refresh saved search set
|
||||
if (!empty($search_request) && isset($_SESSION['search'])) {
|
||||
unset($search_request); // only do this once
|
||||
$_SESSION['search'] = $rcmail->storage->refresh_search();
|
||||
if (!empty($_SESSION['search'][1]->multi)) {
|
||||
$mbox_name = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_POST['_quota'])) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox_name));
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('exists', $rcmail->storage->count($mbox_name, 'EXISTS', true));
|
||||
|
||||
// "No-list" mode, don't get messages
|
||||
if (empty($_POST['_list'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get overall message count; allow caching because rcube_storage::folder_status()
|
||||
// did a refresh but only in list mode
|
||||
$list_mode = $rcmail->storage->get_threading() ? 'THREADS' : 'ALL';
|
||||
$all_count = $rcmail->storage->count($mbox_name, $list_mode, $list_mode == 'THREADS', false);
|
||||
|
||||
// check current page if we're not on the first page
|
||||
if ($all_count && $page > 1) {
|
||||
$remaining = $all_count - $page_size * ($page - 1);
|
||||
if ($remaining <= 0) {
|
||||
$page -= 1;
|
||||
$rcmail->storage->set_page($page);
|
||||
$_SESSION['page'] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('messagecount', $all_count);
|
||||
$rcmail->output->set_env('pagecount', ceil($all_count/$page_size));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($all_count), $mbox_name);
|
||||
$rcmail->output->set_env('current_page', $all_count ? $page : 1);
|
||||
|
||||
// remove old rows (and clear selection if new list is empty)
|
||||
$rcmail->output->command('message_list.clear', $all_count ? false : true);
|
||||
|
||||
if ($all_count) {
|
||||
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
|
||||
// add message rows
|
||||
self::js_message_list($a_headers, false);
|
||||
// remove messages that don't exists from list selection array
|
||||
$rcmail->output->command('update_selection');
|
||||
}
|
||||
|
||||
$list_cleared = true;
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox_name === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox_name, 'EXISTS', true));
|
||||
}
|
||||
}
|
||||
|
||||
// handle flag updates
|
||||
if (!$list_cleared) {
|
||||
$uids = rcube_utils::get_input_value('_uids', rcube_utils::INPUT_POST);
|
||||
$uids = self::get_uids($uids, null, $multifolder);
|
||||
|
||||
$recent_flags = [];
|
||||
|
||||
foreach ($uids as $mbox_name => $set) {
|
||||
$get_flags = true;
|
||||
$modseq = null;
|
||||
|
||||
if ($mbox_name == $current) {
|
||||
$data = $rcmail->storage->folder_data($mbox_name);
|
||||
$modseq = !empty($_SESSION['list_mod_seq']) ? $_SESSION['list_mod_seq'] : null;
|
||||
$get_flags = empty($modseq) || empty($data['HIGHESTMODSEQ']) || $modseq != $data['HIGHESTMODSEQ'];
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider HIGHESTMODSEQ for all folders in multifolder search, otherwise
|
||||
// flags for all messages in a set are requested on every refresh
|
||||
|
||||
if ($get_flags) {
|
||||
$flags = $rcmail->storage->list_flags($mbox_name, $set, $modseq);
|
||||
|
||||
foreach ($flags as $idx => $row) {
|
||||
if ($multifolder) {
|
||||
$idx .= '-' . $mbox_name;
|
||||
}
|
||||
$recent_flags[$idx] = array_change_key_case(array_map('intval', $row));
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('recent_flags', $recent_flags);
|
||||
}
|
||||
}
|
||||
|
||||
// trigger refresh hook
|
||||
$rcmail->plugins->exec_hook('refresh', []);
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
<?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: |
|
||||
| Copy the submitted messages to a specific mailbox |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_copy extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// copy messages
|
||||
if (empty($_POST['_uid']) || !isset($_POST['_target_mbox']) || !strlen($_POST['_target_mbox'])) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
}
|
||||
|
||||
$uids = self::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST);
|
||||
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
|
||||
$sources = [];
|
||||
$copied = false;
|
||||
|
||||
foreach ($uids as $mbox => $uids) {
|
||||
if ($mbox === $target) {
|
||||
$copied++;
|
||||
}
|
||||
else {
|
||||
$copied += (int) $rcmail->storage->copy_message($uids, $target, $mbox);
|
||||
$sources[] = $mbox;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$copied) {
|
||||
self::display_server_error('errorcopying');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('messagecopied', 'confirmation');
|
||||
|
||||
self::send_unread_count($target, true);
|
||||
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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_string('_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 (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
|
||||
if ($next = rcube_utils::get_input_string('_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_string('_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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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: |
|
||||
| Implement folder EXPUNGE request |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_folder_expunge extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
$success = $rcmail->storage->expunge_folder($mbox);
|
||||
|
||||
// reload message list if current mailbox
|
||||
if ($success) {
|
||||
$rcmail->output->show_message('folderexpunged', 'confirmation');
|
||||
|
||||
if (!empty($_REQUEST['_reload'])) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
|
||||
$rcmail->output->command('message_list.clear');
|
||||
$rcmail->action = 'list';
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::display_server_error();
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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: |
|
||||
| Implement folder PURGE request |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_folder_purge extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
$trash_mbox = $rcmail->config->get('trash_mbox');
|
||||
$trash_regexp = '/^' . preg_quote($trash_mbox . $delimiter, '/') . '/';
|
||||
|
||||
// we should only be purging trash (or their subfolders)
|
||||
if (!strlen($trash_mbox) || $mbox === $trash_mbox || preg_match($trash_regexp, $mbox)) {
|
||||
$success = $storage->delete_message('*', $mbox);
|
||||
$delete = true;
|
||||
}
|
||||
// move to Trash
|
||||
else {
|
||||
$success = $storage->move_message('1:*', $trash_mbox, $mbox);
|
||||
$delete = false;
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$rcmail->output->show_message('folderpurged', 'confirmation');
|
||||
$rcmail->output->command('set_unread_count', $mbox, 0);
|
||||
self::set_unseen_count($mbox, 0);
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash_mbox) {
|
||||
$rcmail->output->command('set_trash_count', 0);
|
||||
}
|
||||
else if (strlen($trash_mbox)) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash_mbox, 'EXISTS'));
|
||||
}
|
||||
|
||||
if (!$delete && strlen($trash_mbox)) {
|
||||
self::send_unread_count($trash_mbox, true);
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['_reload'])) {
|
||||
$rcmail->output->set_env('messagecount', 0);
|
||||
$rcmail->output->set_env('pagecount', 0);
|
||||
$rcmail->output->set_env('exists', 0);
|
||||
$rcmail->output->command('message_list.clear');
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text(), $mbox);
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::display_server_error();
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?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: |
|
||||
| Delivering a specific uploaded file or mail message attachment |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_get extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $attachment;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// This resets X-Frame-Options for framed output (#6688)
|
||||
$rcmail->output->page_headers();
|
||||
|
||||
// show loading page
|
||||
if (!empty($_GET['_preload'])) {
|
||||
unset($_GET['_preload']);
|
||||
unset($_GET['_safe']);
|
||||
|
||||
$url = $rcmail->url($_GET + ['_mimewarning' => 1, '_embed' => 1]);
|
||||
$message = $rcmail->gettext('loadingdata');
|
||||
|
||||
header('Content-Type: text/html; charset=' . RCUBE_CHARSET);
|
||||
print "<html>\n<head>\n"
|
||||
. '<meta http-equiv="refresh" content="0; url='.rcube::Q($url).'">' . "\n"
|
||||
. '<meta http-equiv="content-type" content="text/html; charset=' . RCUBE_CHARSET . '">' . "\n"
|
||||
. "</head>\n<body>\n$message\n</body>\n</html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
$attachment = new rcmail_attachment_handler;
|
||||
$mimetype = $attachment->mimetype;
|
||||
$filename = $attachment->filename;
|
||||
|
||||
self::$attachment = $attachment;
|
||||
|
||||
// show part page
|
||||
if (!empty($_GET['_frame'])) {
|
||||
$rcmail->output->set_pagetitle($filename);
|
||||
|
||||
// register UI objects
|
||||
$rcmail->output->add_handlers([
|
||||
'messagepartframe' => [$this, 'message_part_frame'],
|
||||
'messagepartcontrols' => [$this, 'message_part_controls'],
|
||||
]);
|
||||
|
||||
$part_id = rcube_utils::get_input_string('_part', rcube_utils::INPUT_GET);
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
|
||||
// message/rfc822 preview (Note: handle also multipart/ parts, they can
|
||||
// come from Enigma, which replaces message/rfc822 with real mimetype)
|
||||
if ($part_id && ($mimetype == 'message/rfc822' || strpos($mimetype, 'multipart/') === 0)) {
|
||||
$uid = preg_replace('/\.[0-9.]+/', '', $uid);
|
||||
$uid .= '.' . $part_id;
|
||||
|
||||
$rcmail->output->set_env('is_message', true);
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('mailbox', $rcmail->storage->get_folder());
|
||||
$rcmail->output->set_env('uid', $uid);
|
||||
$rcmail->output->set_env('part', $part_id);
|
||||
$rcmail->output->set_env('filename', $filename);
|
||||
$rcmail->output->set_env('mimetype', $mimetype);
|
||||
|
||||
$rcmail->output->send('messagepart');
|
||||
}
|
||||
|
||||
// render thumbnail of an image attachment
|
||||
if (!empty($_GET['_thumb']) && $attachment->is_valid()) {
|
||||
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
|
||||
$file_ident = $attachment->ident;
|
||||
$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) && $attachment->body_to_file($orig_name = rcube_utils::temp_filename('attmnt'))) {
|
||||
$image = new rcube_image($orig_name);
|
||||
|
||||
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
|
||||
$mimetype = 'image/' . $imgtype;
|
||||
}
|
||||
else {
|
||||
// Resize failed, we need to check the file mimetype
|
||||
// So, we do not exit here, but goto generic file body handler below
|
||||
$_GET['_thumb'] = 0;
|
||||
$_REQUEST['_embed'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_GET['_thumb'])) {
|
||||
if (is_file($cache_file)) {
|
||||
$rcmail->output->future_expire_header(3600);
|
||||
header('Content-Type: ' . $mimetype);
|
||||
header('Content-Length: ' . filesize($cache_file));
|
||||
readfile($cache_file);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle attachment body (display or download)
|
||||
if (empty($_GET['_thumb']) && $attachment->is_valid()) {
|
||||
// require CSRF protected url for downloads
|
||||
if (!empty($_GET['_download'])) {
|
||||
$rcmail->request_security_check(rcube_utils::INPUT_GET);
|
||||
}
|
||||
|
||||
$extensions = rcube_mime::get_mime_extensions($mimetype);
|
||||
|
||||
// compare file mimetype with the stated content-type headers and file extension to avoid malicious operations
|
||||
if (!empty($_REQUEST['_embed']) && empty($_REQUEST['_nocheck'])) {
|
||||
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
|
||||
// 1. compare filename suffix with expected suffix derived from mimetype
|
||||
$valid = $file_extension && in_array($file_extension, (array)$extensions)
|
||||
|| empty($extensions)
|
||||
|| !empty($_REQUEST['_mimeclass']);
|
||||
|
||||
// 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension
|
||||
if ($valid || !$file_extension || $mimetype == 'application/octet-stream' || stripos($mimetype, 'text/') === 0) {
|
||||
$tmp_body = $attachment->body(2048);
|
||||
|
||||
// detect message part mimetype
|
||||
$real_mimetype = rcube_mime::file_content_type($tmp_body, $filename, $mimetype, true, true);
|
||||
list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype);
|
||||
|
||||
// accept text/plain with any extension
|
||||
if ($real_mimetype == 'text/plain' && self::mimetype_compare($real_mimetype, $mimetype)) {
|
||||
$valid_extension = true;
|
||||
}
|
||||
// ignore differences in text/* mimetypes. Filetype detection isn't very reliable here
|
||||
else if ($real_ctype_primary == 'text' && strpos($mimetype, $real_ctype_primary) === 0) {
|
||||
$real_mimetype = $mimetype;
|
||||
$valid_extension = true;
|
||||
}
|
||||
// ignore filename extension if mimeclass matches (#1489029)
|
||||
else if (!empty($_REQUEST['_mimeclass']) && $real_ctype_primary == $_REQUEST['_mimeclass']) {
|
||||
$valid_extension = true;
|
||||
}
|
||||
else {
|
||||
// get valid file extensions
|
||||
$extensions = rcube_mime::get_mime_extensions($real_mimetype);
|
||||
$valid_extension = !$file_extension || empty($extensions) || in_array($file_extension, (array)$extensions);
|
||||
}
|
||||
|
||||
if (
|
||||
// fix mimetype for files wrongly declared as octet-stream
|
||||
($mimetype == 'application/octet-stream' && $valid_extension)
|
||||
// force detected mimetype for images (#8158)
|
||||
|| (strpos($real_mimetype, 'image/') === 0)
|
||||
) {
|
||||
$mimetype = $real_mimetype;
|
||||
}
|
||||
|
||||
// "fix" real mimetype the same way the original is before comparison
|
||||
$real_mimetype = rcube_mime::fix_mimetype($real_mimetype);
|
||||
|
||||
$valid = $valid_extension && self::mimetype_compare($real_mimetype, $mimetype);
|
||||
}
|
||||
else {
|
||||
$real_mimetype = $mimetype;
|
||||
}
|
||||
|
||||
// show warning if validity checks failed
|
||||
if (!$valid) {
|
||||
// send blocked.gif for expected images
|
||||
if (empty($_REQUEST['_mimewarning']) && strpos($mimetype, 'image/') === 0) {
|
||||
// Do not cache. Failure might be the result of a misconfiguration,
|
||||
// thus real content should be returned once fixed.
|
||||
$content = self::get_resource_content('blocked.gif');
|
||||
$rcmail->output->nocacheing_headers();
|
||||
header("Content-Type: image/gif");
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
header("Content-Length: " . strlen($content));
|
||||
echo $content;
|
||||
}
|
||||
// html warning with a button to load the file anyway
|
||||
else {
|
||||
$rcmail->output = new rcmail_html_page();
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext([
|
||||
'name' => 'attachmentvalidationerror',
|
||||
'vars' => [
|
||||
'expected' => $mimetype . (!empty($file_extension) ? rcube::Q(" (.{$file_extension})") : ''),
|
||||
'detected' => $real_mimetype . (!empty($extensions[0]) ? " (.{$extensions[0]})" : ''),
|
||||
]
|
||||
]),
|
||||
$rcmail->gettext('showanyway'),
|
||||
$rcmail->url(array_merge($_GET, ['_nocheck' => 1]))
|
||||
);
|
||||
|
||||
$rcmail->output->write();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// TIFF/WEBP to JPEG conversion, if needed
|
||||
foreach (['tiff', 'webp'] as $type) {
|
||||
$img_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps'][$type]);
|
||||
if (
|
||||
!empty($_REQUEST['_embed'])
|
||||
&& !$img_support
|
||||
&& $attachment->image_type() == 'image/' . $type
|
||||
&& rcube_image::is_convertable('image/' . $type)
|
||||
) {
|
||||
$convert2jpeg = true;
|
||||
$mimetype = 'image/jpeg';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// deliver part content
|
||||
if ($mimetype == 'text/html' && empty($_GET['_download'])) {
|
||||
$rcmail->output = new rcmail_html_page();
|
||||
$out = '';
|
||||
|
||||
// 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($attachment->size * 10)) {
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext('messagetoobig'),
|
||||
$rcmail->gettext('download'),
|
||||
$rcmail->url(array_merge($_GET, ['_download' => 1]))
|
||||
);
|
||||
}
|
||||
else {
|
||||
// render HTML body
|
||||
$out = $attachment->html();
|
||||
|
||||
// insert remote objects warning into HTML body
|
||||
if (self::$REMOTE_OBJECTS) {
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext('blockedresources'),
|
||||
$rcmail->gettext('allow'),
|
||||
$rcmail->url(array_merge($_GET, ['_safe' => 1]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->write($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
// add filename extension if missing
|
||||
if (!pathinfo($filename, PATHINFO_EXTENSION) && ($extensions = rcube_mime::get_mime_extensions($mimetype))) {
|
||||
$filename .= '.' . $extensions[0];
|
||||
}
|
||||
|
||||
$rcmail->output->download_headers($filename, [
|
||||
'type' => $mimetype,
|
||||
'type_charset' => $attachment->charset,
|
||||
'disposition' => !empty($_GET['_download']) ? 'attachment' : 'inline',
|
||||
]);
|
||||
|
||||
// handle tiff to jpeg conversion
|
||||
if (!empty($convert2jpeg)) {
|
||||
$file_path = rcube_utils::temp_filename('attmnt');
|
||||
|
||||
// convert image to jpeg and send it to the browser
|
||||
if ($attachment->body_to_file($file_path)) {
|
||||
$image = new rcube_image($file_path);
|
||||
if ($image->convert(rcube_image::TYPE_JPG, $file_path)) {
|
||||
header("Content-Length: " . filesize($file_path));
|
||||
readfile($file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$attachment->output($mimetype);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// if we arrive here, the requested part was not found
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two mimetype strings with making sure that
|
||||
* e.g. image/bmp and image/x-ms-bmp are treated as equal.
|
||||
*/
|
||||
public static function mimetype_compare($type1, $type2)
|
||||
{
|
||||
$regexp = '~/(x-ms-|x-)~';
|
||||
$type1 = preg_replace($regexp, '/', $type1);
|
||||
$type2 = preg_replace($regexp, '/', $type2);
|
||||
|
||||
return $type1 === $type2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment properties table
|
||||
*/
|
||||
public static function message_part_controls($attrib)
|
||||
{
|
||||
if (!self::$attachment->is_valid()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('namex')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->filename));
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('type')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->mimetype));
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('size')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->size()));
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment preview frame
|
||||
*/
|
||||
public static function message_part_frame($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($rcmail->output->get_env('is_message')) {
|
||||
$url = [
|
||||
'task' => 'mail',
|
||||
'action' => 'preview',
|
||||
'uid' => $rcmail->output->get_env('uid'),
|
||||
'mbox' => $rcmail->output->get_env('mailbox'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$mimetype = $rcmail->output->get_env('mimetype');
|
||||
$url = $_GET;
|
||||
$url[strpos($mimetype, 'text/') === 0 ? '_embed' : '_preload'] = 1;
|
||||
unset($url['_frame']);
|
||||
}
|
||||
|
||||
$url['_framed'] = 1; // For proper X-Frame-Options:deny handling
|
||||
|
||||
$attrib['src'] = $rcmail->url($url);
|
||||
|
||||
$rcmail->output->add_gui_object('messagepartframe', $attrib['id']);
|
||||
|
||||
return html::iframe($attrib);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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: |
|
||||
| Check all mailboxes for unread messages and update GUI |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_getunread extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$a_folders = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
|
||||
if (!empty($a_folders)) {
|
||||
$current = $rcmail->storage->get_folder();
|
||||
$inbox = $current == 'INBOX';
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$check_all = (bool) $rcmail->config->get('check_all_folders');
|
||||
|
||||
foreach ($a_folders as $mbox) {
|
||||
$unseen_old = self::get_unseen_count($mbox);
|
||||
|
||||
if (!$check_all && $unseen_old !== null && $mbox != $current) {
|
||||
$unseen = $unseen_old;
|
||||
}
|
||||
else {
|
||||
$unseen = $rcmail->storage->count($mbox, 'UNSEEN', $unseen_old === null);
|
||||
}
|
||||
|
||||
// call it always for current folder, so it can update counter
|
||||
// after possible message status change when opening a message
|
||||
// not in preview frame
|
||||
if ($unseen || $unseen_old === null || $mbox == $current) {
|
||||
$rcmail->output->command('set_unread_count', $mbox, $unseen, $inbox && $mbox == 'INBOX');
|
||||
}
|
||||
|
||||
self::set_unseen_count($mbox, $unseen);
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox, 'EXISTS'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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: |
|
||||
| Expand addressbook group into list of email addresses |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_group_expand extends rcmail_action
|
||||
{
|
||||
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();
|
||||
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET);
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$abook = $rcmail->get_address_book($source);
|
||||
|
||||
if ($gid && $abook) {
|
||||
$abook->set_group($gid);
|
||||
$abook->set_pagesize(9999); // TODO: limit number of group members by config?
|
||||
|
||||
$result = $abook->list_records($rcmail->config->get('contactlist_fields'));
|
||||
$members = [];
|
||||
|
||||
while ($result && ($record = $result->iterate())) {
|
||||
$email = array_first((array) $abook->get_col_values('email', $record, true));
|
||||
if (!empty($email)) {
|
||||
$members[] = format_email_recipient($email, rcube_addressbook::compose_list_name($record));
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->command('replace_group_recipients', $gid, join(', ', array_unique($members)));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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: |
|
||||
| Fetch message headers in raw format for display |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_headers extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $source;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
|
||||
$inline = $rcmail->output instanceof rcmail_output_html;
|
||||
|
||||
if (!$uid) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($pos = strpos($uid, '.')) {
|
||||
$message = new rcube_message($uid);
|
||||
$source = $message->get_part_body(substr($uid, $pos + 1));
|
||||
$source = substr($source, 0, strpos($source, "\r\n\r\n"));
|
||||
}
|
||||
else {
|
||||
$source = $rcmail->storage->get_raw_headers($uid);
|
||||
}
|
||||
|
||||
if ($source !== false) {
|
||||
$source = trim(rcube_charset::clean($source));
|
||||
$source = htmlspecialchars($source, ENT_COMPAT | ENT_HTML401, RCUBE_CHARSET);
|
||||
$source = preg_replace(
|
||||
[
|
||||
'/\n[\t\s]+/',
|
||||
'/^([a-z0-9_:-]+)/im',
|
||||
'/\r?\n/'
|
||||
],
|
||||
[
|
||||
"\n ",
|
||||
'<font class="bold">\1</font>',
|
||||
'<br />'
|
||||
],
|
||||
$source
|
||||
);
|
||||
|
||||
self::$source = $source;
|
||||
|
||||
$rcmail->output->add_handlers(['dialogcontent' => [$this, 'headers_output']]);
|
||||
|
||||
if ($inline) {
|
||||
$rcmail->output->set_env('dialog_class', 'text-nowrap');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('set_headers', $source);
|
||||
}
|
||||
}
|
||||
else if (!$inline) {
|
||||
$rcmail->output->show_message('messageopenerror', 'error');
|
||||
}
|
||||
|
||||
$rcmail->output->send($inline ? 'dialog' : null);
|
||||
}
|
||||
|
||||
public static function headers_output()
|
||||
{
|
||||
return self::$source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?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: |
|
||||
| Save the uploaded file(s) as messages to the current IMAP folder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_import extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
if (!empty($_FILES['_file']) && is_array($_FILES['_file'])) {
|
||||
$imported = 0;
|
||||
$folder = $rcmail->storage->get_folder();
|
||||
|
||||
foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded file if there is no error
|
||||
$err = $_FILES['_file']['error'][$i];
|
||||
|
||||
if (!$err) {
|
||||
// check file content type first
|
||||
$ctype = rcube_mime::file_content_type($filepath, $_FILES['_file']['name'][$i], $_FILES['_file']['type'][$i]);
|
||||
list($mtype_primary, $mtype_secondary) = explode('/', $ctype);
|
||||
|
||||
if (in_array($ctype, ['application/zip', 'application/x-zip'])) {
|
||||
$filepath = self::zip_extract($filepath);
|
||||
if (empty($filepath)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!in_array($mtype_primary, ['text', 'message'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ((array) $filepath as $file) {
|
||||
// read the first few lines to detect header-like structure
|
||||
$fp = fopen($file, 'r');
|
||||
do {
|
||||
$line = fgets($fp);
|
||||
}
|
||||
while ($line !== false && trim($line) == '');
|
||||
|
||||
if (!preg_match('/^From .+/', $line) && !preg_match('/^[a-z-_]+:\s+.+/i', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = $lastline = '';
|
||||
fseek($fp, 0);
|
||||
|
||||
while (($line = fgets($fp)) !== false) {
|
||||
// importing mbox file, split by From - lines
|
||||
if ($lastline === '' && strncmp($line, 'From ', 5) === 0 && strlen($line) > 5) {
|
||||
if (!empty($message)) {
|
||||
$imported += (int) self::save_message($folder, $message);
|
||||
}
|
||||
|
||||
$message = $line;
|
||||
$lastline = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
$message .= $line;
|
||||
$lastline = rtrim($line);
|
||||
}
|
||||
|
||||
if (!empty($message)) {
|
||||
$imported += (int) self::save_message($folder, $message);
|
||||
}
|
||||
|
||||
// remove temp files extracted from zip
|
||||
if (is_array($filepath)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::upload_error($err);
|
||||
}
|
||||
}
|
||||
|
||||
if ($imported) {
|
||||
$rcmail->output->show_message($rcmail->gettext(['name' => 'importmessagesuccess', 'nr' => $imported, 'vars' => ['nr' => $imported]]), 'confirmation');
|
||||
$rcmail->output->command('command', 'list');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('importmessageerror', 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::upload_failure();
|
||||
}
|
||||
|
||||
// send html page with JS calls as response
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function zip_extract($path)
|
||||
{
|
||||
if (!class_exists('ZipArchive', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$zip = new ZipArchive;
|
||||
$files = [];
|
||||
|
||||
if ($zip->open($path)) {
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entry = $zip->getNameIndex($i);
|
||||
$tmpfname = rcube_utils::temp_filename('zipimport');
|
||||
|
||||
if (copy("zip://$path#$entry", $tmpfname)) {
|
||||
$ctype = rcube_mime::file_content_type($tmpfname, $entry);
|
||||
list($mtype_primary, ) = explode('/', $ctype);
|
||||
|
||||
if (in_array($mtype_primary, ['text', 'message'])) {
|
||||
$files[] = $tmpfname;
|
||||
}
|
||||
else {
|
||||
unlink($tmpfname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public static function save_message($folder, &$message)
|
||||
{
|
||||
$date = null;
|
||||
|
||||
if (strncmp($message, 'From ', 5) === 0) {
|
||||
// Extract the mbox from_line
|
||||
$pos = strpos($message, "\n");
|
||||
$from = substr($message, 0, $pos);
|
||||
$message = substr($message, $pos + 1);
|
||||
|
||||
// Read the received date, support only known date formats
|
||||
|
||||
// RFC4155: "Sat Jan 3 01:05:34 1996"
|
||||
$mboxdate_rx = '/^([a-z]{3} [a-z]{3} [0-9 ][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4})/i';
|
||||
// Roundcube/Zipdownload: "12-Dec-2016 10:56:33 +0100"
|
||||
$imapdate_rx = '/^([0-9]{1,2}-[a-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9+-]{5})/i';
|
||||
|
||||
if (
|
||||
($pos = strpos($from, ' ', 6))
|
||||
&& ($dt_str = substr($from, $pos + 1))
|
||||
&& (preg_match($mboxdate_rx, $dt_str, $m) || preg_match($imapdate_rx, $dt_str, $m))
|
||||
) {
|
||||
try {
|
||||
$date = new DateTime($m[0], new DateTimeZone('UTC'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unquote ">From " lines in message body
|
||||
$message = preg_replace('/\n>([>]*)From /', "\n\\1From ", $message);
|
||||
$message = rtrim($message);
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($rcmail->storage->save_message($folder, $message, '', false, [], $date)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
rcube::raise_error("Failed to import message to $folder", true, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
<?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: |
|
||||
| Send message list to client (as remote response) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_list 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();
|
||||
$save_arr = [];
|
||||
$dont_override = (array) $rcmail->config->get('dont_override');
|
||||
$cols = null;
|
||||
|
||||
// is there a sort type for this request?
|
||||
$sort = rcube_utils::get_input_string('_sort', rcube_utils::INPUT_GET);
|
||||
if ($sort && preg_match('/^[a-zA-Z_-]+$/', $sort)) {
|
||||
// yes, so set the sort vars
|
||||
list($sort_col, $sort_order) = explode('_', $sort);
|
||||
|
||||
// set session vars for sort (so next page and task switch know how to sort)
|
||||
if (!in_array('message_sort_col', $dont_override)) {
|
||||
$_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
|
||||
}
|
||||
if (!in_array('message_sort_order', $dont_override)) {
|
||||
$_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
|
||||
}
|
||||
}
|
||||
|
||||
// is there a set of columns for this request?
|
||||
if ($cols = rcube_utils::get_input_string('_cols', rcube_utils::INPUT_GET)) {
|
||||
$_SESSION['list_attrib']['columns'] = explode(',', $cols);
|
||||
if (!in_array('list_cols', $dont_override)) {
|
||||
$save_arr['list_cols'] = explode(',', $cols);
|
||||
}
|
||||
}
|
||||
|
||||
// register layout change
|
||||
if ($layout = rcube_utils::get_input_string('_layout', rcube_utils::INPUT_GET)) {
|
||||
$rcmail->output->set_env('layout', $layout);
|
||||
$save_arr['layout'] = $layout;
|
||||
// force header replace on layout change
|
||||
if (!empty($_SESSION['list_attrib']['columns'])) {
|
||||
$cols = $_SESSION['list_attrib']['columns'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($save_arr)) {
|
||||
$rcmail->user->save_prefs($save_arr);
|
||||
}
|
||||
|
||||
$mbox_name = $rcmail->storage->get_folder();
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
|
||||
// Synchronize mailbox cache, handle flag changes
|
||||
$rcmail->storage->folder_sync($mbox_name);
|
||||
|
||||
// fetch message headers
|
||||
$a_headers = [];
|
||||
if ($count = $rcmail->storage->count($mbox_name, $threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) {
|
||||
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
|
||||
}
|
||||
|
||||
// update search set (possible change of threading mode)
|
||||
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
|
||||
&& $_SESSION['search_request'] == $_REQUEST['_search']
|
||||
) {
|
||||
$search_request = $_REQUEST['_search'];
|
||||
$_SESSION['search'] = $rcmail->storage->get_search_set();
|
||||
$multifolder = !empty($_SESSION['search']) && !empty($_SESSION['search'][1]->multi);
|
||||
}
|
||||
// remove old search data
|
||||
else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) {
|
||||
$rcmail->session->remove('search');
|
||||
}
|
||||
|
||||
self::list_pagetitle();
|
||||
|
||||
// update mailboxlist
|
||||
if (empty($search_request)) {
|
||||
self::send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), empty($a_headers) ? 0 : null);
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$pages = ceil($count / $rcmail->storage->get_pagesize());
|
||||
$page = $count ? $rcmail->storage->get_page() : 1;
|
||||
$exists = $rcmail->storage->count($mbox_name, 'EXISTS', true);
|
||||
|
||||
$rcmail->output->set_env('messagecount', $count);
|
||||
$rcmail->output->set_env('pagecount', $pages);
|
||||
$rcmail->output->set_env('threading', $threading);
|
||||
$rcmail->output->set_env('current_page', $page);
|
||||
$rcmail->output->set_env('exists', $exists);
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count), $mbox_name);
|
||||
|
||||
// remove old message rows if commanded by the client
|
||||
if (!empty($_REQUEST['_clear'])) {
|
||||
$rcmail->output->command('clear_message_list');
|
||||
}
|
||||
|
||||
// add message rows
|
||||
self::js_message_list($a_headers, false, $cols);
|
||||
|
||||
if (!empty($a_headers)) {
|
||||
if (!empty($search_request)) {
|
||||
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $count]);
|
||||
}
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
// we need it for flag updates in check-recent
|
||||
$data = $rcmail->storage->folder_data($mbox_name);
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// handle IMAP errors (e.g. #1486905)
|
||||
if ($err_code = $rcmail->storage->get_error_code()) {
|
||||
self::display_server_error();
|
||||
}
|
||||
else if (!empty($search_request)) {
|
||||
$rcmail->output->show_message('searchnomatch', 'notice');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('nomessagesfound', 'notice');
|
||||
}
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox_name === $rcmail->config->get('trash_mbox')) {
|
||||
$rcmail->output->command('set_trash_count', $exists);
|
||||
}
|
||||
|
||||
if ($page == 1) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, !empty($multifolder) ? 'INBOX' : $mbox_name));
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?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: |
|
||||
| Send contacts list to client (as remote response) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_list_contacts 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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$list_page = max(1, $_GET['_page'] ?? 0);
|
||||
$jsresult = [];
|
||||
|
||||
// Use search result
|
||||
if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) {
|
||||
$search = (array) $_SESSION['contact_search'][$_REQUEST['_search']];
|
||||
$sparam = $_SESSION['contact_search_params']['id'] == $_REQUEST['_search'] ? $_SESSION['contact_search_params']['data'] : [];
|
||||
$mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$records = [];
|
||||
|
||||
// get records from all sources
|
||||
foreach ($search as $s => $set) {
|
||||
$CONTACTS = $rcmail->get_address_book($s);
|
||||
|
||||
// list matching groups of this source (on page one)
|
||||
if ($sparam[1] && $CONTACTS->groups && $list_page == 1) {
|
||||
$jsresult += self::compose_contact_groups($CONTACTS, $s, $sparam[1], $mode);
|
||||
}
|
||||
|
||||
// reset page
|
||||
$CONTACTS->set_page(1);
|
||||
$CONTACTS->set_pagesize(9999);
|
||||
$CONTACTS->set_search_set($set);
|
||||
|
||||
// get records
|
||||
$result = $CONTACTS->list_records($afields);
|
||||
|
||||
while ($row = $result->next()) {
|
||||
$row['sourceid'] = $s;
|
||||
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
|
||||
$records[$key] = $row;
|
||||
}
|
||||
unset($result);
|
||||
}
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$first = ($list_page-1) * $page_size;
|
||||
$result = new rcube_result_set($count, $first);
|
||||
|
||||
// we need only records for current page
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $first, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
}
|
||||
// list contacts from selected source
|
||||
else {
|
||||
$CONTACTS = $rcmail->get_address_book($source);
|
||||
|
||||
if ($CONTACTS && $CONTACTS->ready) {
|
||||
// set list properties
|
||||
$CONTACTS->set_pagesize($page_size);
|
||||
$CONTACTS->set_page($list_page);
|
||||
|
||||
if ($group_id = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET)) {
|
||||
$CONTACTS->set_group($group_id);
|
||||
}
|
||||
// list groups of this source (on page one)
|
||||
else if ($CONTACTS->groups && $CONTACTS->list_page == 1) {
|
||||
$jsresult = self::compose_contact_groups($CONTACTS, $source);
|
||||
}
|
||||
|
||||
// get contacts for this user
|
||||
$result = $CONTACTS->list_records($afields);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result) && !$result->count && $result->searchonly) {
|
||||
$rcmail->output->show_message('contactsearchonly', 'notice');
|
||||
}
|
||||
else if (!empty($result) && $result->count > 0) {
|
||||
// create javascript list
|
||||
while ($row = $result->next()) {
|
||||
$name = rcube_addressbook::compose_list_name($row);
|
||||
|
||||
// add record for every email address of the contact
|
||||
$emails = rcube_addressbook::get_col_values('email', $row, true);
|
||||
foreach ($emails as $i => $email) {
|
||||
$source = !empty($row['sourceid']) ? $row['sourceid'] : $source;
|
||||
$row_id = $source.'-'.$row['ID'].'-'.$i;
|
||||
$is_group = isset($row['_type']) && $row['_type'] == 'group';
|
||||
$classname = $is_group ? 'group' : 'person';
|
||||
$keyname = $is_group ? 'contactgroup' : 'contact';
|
||||
|
||||
$jsresult[$row_id] = format_email_recipient($email, $name);
|
||||
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
$keyname => html::a(
|
||||
['title' => $email],
|
||||
rcube::Q($name ?: $email)
|
||||
. ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '')
|
||||
)
|
||||
],
|
||||
$classname
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update env
|
||||
$rcmail->output->set_env('contactdata', $jsresult);
|
||||
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 1);
|
||||
$rcmail->output->command('set_page_buttons');
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add groups from the given address source to the address book widget
|
||||
*/
|
||||
public static function compose_contact_groups($abook, $source_id, $search = null, $search_mode = 0)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$jsresult = [];
|
||||
|
||||
foreach ($abook->list_groups($search, $search_mode) as $group) {
|
||||
$abook->reset();
|
||||
$abook->set_group($group['ID']);
|
||||
|
||||
// group (distribution list) with email address(es)
|
||||
if (!empty($group['email'])) {
|
||||
foreach ((array) $group['email'] as $email) {
|
||||
$row_id = 'G'.$group['ID'];
|
||||
$jsresult[$row_id] = format_email_recipient($email, $group['name']);
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => html::span(['title' => $email], rcube::Q($group['name']))
|
||||
], 'group');
|
||||
}
|
||||
}
|
||||
// make virtual groups clickable to list their members
|
||||
else if (!empty($group['virtual'])) {
|
||||
$row_id = 'G'.$group['ID'];
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => html::a([
|
||||
'href' => '#list',
|
||||
'rel' => $group['ID'],
|
||||
'title' => $rcmail->gettext('listgroup'),
|
||||
'onclick' => sprintf("return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)",
|
||||
rcmail_output::JS_OBJECT_NAME, $source_id, $group['ID']),
|
||||
],
|
||||
rcube::Q($group['name']) . ' ' . html::span('action', '»')
|
||||
)],
|
||||
'group',
|
||||
['ID' => $group['ID'], 'name' => $group['name'], 'virtual' => true]
|
||||
);
|
||||
}
|
||||
// show group with count
|
||||
else if (($result = $abook->count()) && $result->count) {
|
||||
$row_id = 'E'.$group['ID'];
|
||||
$jsresult[$row_id] = ['name' => $group['name'], 'source' => $source_id];
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => rcube::Q($group['name'] . ' (' . intval($result->count) . ')')
|
||||
], 'group');
|
||||
}
|
||||
}
|
||||
|
||||
$abook->reset();
|
||||
$abook->set_group(0);
|
||||
|
||||
return $jsresult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?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: |
|
||||
| Mark the submitted messages with the specified flag |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_mark 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();
|
||||
$_uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
|
||||
$flag = rcube_utils::get_input_string('_flag', rcube_utils::INPUT_POST);
|
||||
$folders = rcube_utils::get_input_string('_folders', rcube_utils::INPUT_POST);
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST);
|
||||
|
||||
if (empty($_uids) || empty($flag)) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
$skip_deleted = (bool) $rcmail->config->get('skip_deleted');
|
||||
$read_deleted = (bool) $rcmail->config->get('read_when_deleted');
|
||||
$flag = self::imap_flag($flag);
|
||||
$old_count = 0;
|
||||
$from = $_POST['_from'] ?? null;
|
||||
|
||||
if ($flag == 'DELETED' && $skip_deleted && $from != 'show') {
|
||||
// count messages before changing anything
|
||||
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
}
|
||||
|
||||
if ($folders == 'all') {
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
|
||||
}
|
||||
else if ($folders == 'sub') {
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
|
||||
array_unshift($mboxes, $mbox);
|
||||
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
|
||||
}
|
||||
else if ($folders == 'cur') {
|
||||
$input = [$mbox => '*'];
|
||||
}
|
||||
else {
|
||||
$input = self::get_uids(null, null, $dummy, rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
$marked = 0;
|
||||
$count = 0;
|
||||
$read = 0;
|
||||
|
||||
foreach ($input as $mbox => $uids) {
|
||||
$marked += (int) $rcmail->storage->set_flag($uids, $flag, $mbox);
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
}
|
||||
|
||||
if (!$marked) {
|
||||
// send error message
|
||||
if ($from != 'show') {
|
||||
$rcmail->output->command('list_mailbox');
|
||||
}
|
||||
|
||||
self::display_server_error('errormarking');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else if (empty($_POST['_quiet'])) {
|
||||
$rcmail->output->show_message('messagemarked', 'confirmation');
|
||||
}
|
||||
|
||||
if ($flag == 'DELETED' && $read_deleted && !empty($_POST['_ruid'])) {
|
||||
if ($ruids = rcube_utils::get_input_value('_ruid', rcube_utils::INPUT_POST)) {
|
||||
foreach (self::get_uids($ruids) as $mbox => $uids) {
|
||||
$read += (int) $rcmail->storage->set_flag($uids, 'SEEN', $mbox);
|
||||
}
|
||||
}
|
||||
|
||||
if ($read && !$skip_deleted) {
|
||||
$rcmail->output->command('flag_deleted_as_read', $ruids);
|
||||
}
|
||||
}
|
||||
|
||||
if ($flag == 'SEEN' || $flag == 'UNSEEN' || ($flag == 'DELETED' && !$skip_deleted)) {
|
||||
foreach ($input as $mbox => $uids) {
|
||||
self::send_unread_count($mbox);
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('last_flag', $flag);
|
||||
}
|
||||
else if ($flag == 'DELETED' && $skip_deleted) {
|
||||
if ($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');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$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();
|
||||
}
|
||||
|
||||
$msg_count = $rcmail->storage->count(NULL, $threading ? 'THREADS' : 'ALL');
|
||||
$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;
|
||||
}
|
||||
|
||||
foreach ($input as $mbox => $uids) {
|
||||
self::send_unread_count($mbox, 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->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 ($old_count && $_uids != '*' && ($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Roundcube UI's flag label into IMAP flag
|
||||
*
|
||||
* @param string $flag Flag label
|
||||
*
|
||||
* @return string Uppercase IMAP flag
|
||||
*/
|
||||
public static function imap_flag($flag)
|
||||
{
|
||||
$flags_map = [
|
||||
'undelete' => 'UNDELETED',
|
||||
'delete' => 'DELETED',
|
||||
'read' => 'SEEN',
|
||||
'unread' => 'UNSEEN',
|
||||
'flagged' => 'FLAGGED',
|
||||
'unflagged' => 'UNFLAGGED',
|
||||
];
|
||||
|
||||
return !empty($flags_map[$flag]) ? $flags_map[$flag] : strtoupper($flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?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 move operation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_move 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');
|
||||
$old_count = 0;
|
||||
|
||||
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
|
||||
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
}
|
||||
|
||||
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (empty($_POST['_uid']) || !strlen($target)) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$addrows = false;
|
||||
$count = 0;
|
||||
$sources = [];
|
||||
|
||||
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
|
||||
if ($mbox === $target) {
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
}
|
||||
else if ($rcmail->storage->move_message($uids, $target, $mbox)) {
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
$sources[] = $mbox;
|
||||
}
|
||||
else {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
// send error message
|
||||
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
|
||||
$rcmail->output->command('list_mailbox');
|
||||
}
|
||||
|
||||
self::display_server_error('errormoving', null, $target == $trash ? 'delete' : '');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message($target == $trash ? 'messagemovedtotrash' : 'messagemoved', 'confirmation');
|
||||
}
|
||||
|
||||
if (!empty($_POST['_refresh'])) {
|
||||
// FIXME: send updated message rows instead of reloading the entire list
|
||||
$rcmail->output->command('refresh_list');
|
||||
}
|
||||
else {
|
||||
$addrows = true;
|
||||
}
|
||||
|
||||
$search_request = rcube_utils::get_input_string('_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 (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
|
||||
if ($next = rcube_utils::get_input_string('_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 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);
|
||||
}
|
||||
|
||||
self::send_unread_count($target, 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_string('_count', rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if ($addrows && $count && $_POST['_uid'] != '*' && (!empty($jump_back) || $nextpage_count > 0)) {
|
||||
// #5862: Don't add more rows than it was on the next page
|
||||
$count = !empty($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);
|
||||
}
|
||||
else if ($target === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash, 'EXISTS', true));
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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: |
|
||||
| Updates message page navigation controls |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_pagenav 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();
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
$index = $rcmail->storage->index(null, self::sort_column(), self::sort_order());
|
||||
$cnt = $index->count_messages();
|
||||
|
||||
if ($cnt && ($pos = $index->exists($uid, true)) !== false) {
|
||||
$prev = $pos ? $index->get_element($pos-1) : 0;
|
||||
$first = $pos ? $index->get_element('FIRST') : 0;
|
||||
$next = $pos < $cnt-1 ? $index->get_element($pos+1) : 0;
|
||||
$last = $pos < $cnt-1 ? $index->get_element('LAST') : 0;
|
||||
}
|
||||
else {
|
||||
// error, this will at least disable page navigation
|
||||
$rcmail->output->command('set_rowcount', '');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
// Set UIDs and activate navigation buttons
|
||||
if (!empty($prev)) {
|
||||
$rcmail->output->set_env('prev_uid', $prev);
|
||||
$rcmail->output->command('enable_command', 'previousmessage', 'firstmessage', true);
|
||||
}
|
||||
|
||||
if (!empty($next)) {
|
||||
$rcmail->output->set_env('next_uid', $next);
|
||||
$rcmail->output->command('enable_command', 'nextmessage', 'lastmessage', true);
|
||||
}
|
||||
|
||||
if (!empty($first)) {
|
||||
$rcmail->output->set_env('first_uid', $first);
|
||||
}
|
||||
|
||||
if (!empty($last)) {
|
||||
$rcmail->output->set_env('last_uid', $last);
|
||||
}
|
||||
|
||||
// Don't need a real messages count value
|
||||
$rcmail->output->set_env('messagecount', 1);
|
||||
|
||||
// Set rowcount text
|
||||
$rcmail->output->command('set_rowcount', $rcmail->gettext([
|
||||
'name' => 'messagenrof',
|
||||
'vars' => ['nr' => ($pos ?? 0) + 1, 'count' => $cnt]
|
||||
]));
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?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: |
|
||||
| Mail messages search action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Benjamin Smith <defitro@gmail.com> |
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_search 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();
|
||||
|
||||
@set_time_limit(170); // extend default max_execution_time to ~3 minutes
|
||||
|
||||
// reset list_page and old search results
|
||||
$rcmail->storage->set_page(1);
|
||||
$rcmail->storage->set_search_set(null);
|
||||
$_SESSION['page'] = 1;
|
||||
|
||||
// get search string
|
||||
$str = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GET, true);
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GET, true);
|
||||
$filter = rcube_utils::get_input_string('_filter', rcube_utils::INPUT_GET);
|
||||
$headers = rcube_utils::get_input_string('_headers', rcube_utils::INPUT_GET);
|
||||
$scope = rcube_utils::get_input_string('_scope', rcube_utils::INPUT_GET);
|
||||
$interval = rcube_utils::get_input_string('_interval', rcube_utils::INPUT_GET);
|
||||
$continue = rcube_utils::get_input_string('_continue', rcube_utils::INPUT_GET);
|
||||
|
||||
$filter = trim((string) $filter);
|
||||
$search_request = md5($mbox . $scope . $interval . $filter . $str);
|
||||
|
||||
// Parse input
|
||||
list($subject, $search) = self::search_input($str, $headers, $scope, $mbox);
|
||||
|
||||
// add list filter string
|
||||
$search_str = $filter && $filter != 'ALL' ? $filter : '';
|
||||
|
||||
if ($search_interval = self::search_interval_criteria($interval)) {
|
||||
$search_str .= ' ' . $search_interval;
|
||||
}
|
||||
|
||||
if (!empty($subject)) {
|
||||
$search_str .= str_repeat(' OR', count($subject)-1);
|
||||
foreach ($subject as $sub) {
|
||||
$search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search);
|
||||
}
|
||||
}
|
||||
|
||||
$search_str = trim($search_str);
|
||||
$sort_column = self::sort_column();
|
||||
$sort_order = self::sort_order();
|
||||
|
||||
// set message set for already stored (but incomplete) search request
|
||||
if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
|
||||
$rcmail->storage->set_search_set($_SESSION['search']);
|
||||
$search_str = $_SESSION['search'][0];
|
||||
}
|
||||
|
||||
// execute IMAP search
|
||||
if ($search_str) {
|
||||
$mboxes = [];
|
||||
|
||||
// search all, current or subfolders folders
|
||||
if ($scope == 'all') {
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail', null, true);
|
||||
// we want natural alphabetic sorting of folders in the result set
|
||||
natcasesort($mboxes);
|
||||
}
|
||||
else if ($scope == 'sub') {
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
|
||||
array_unshift($mboxes, $mbox);
|
||||
}
|
||||
|
||||
if ($scope != 'all') {
|
||||
// Remember current folder, it can change in meantime (plugins)
|
||||
// but we need it to e.g. recognize Sent folder to handle From/To column later
|
||||
$rcmail->output->set_env('mailbox', $mbox);
|
||||
}
|
||||
|
||||
$result = $rcmail->storage->search($mboxes, $search_str, RCUBE_CHARSET, $sort_column);
|
||||
}
|
||||
|
||||
// save search results in session
|
||||
if (!isset($_SESSION['search']) || !is_array($_SESSION['search'])) {
|
||||
$_SESSION['search'] = [];
|
||||
}
|
||||
|
||||
if ($search_str) {
|
||||
$_SESSION['search'] = $rcmail->storage->get_search_set();
|
||||
$_SESSION['last_text_search'] = $str;
|
||||
}
|
||||
|
||||
$_SESSION['search_request'] = $search_request;
|
||||
$_SESSION['search_scope'] = $scope;
|
||||
$_SESSION['search_interval'] = $interval;
|
||||
$_SESSION['search_filter'] = $filter;
|
||||
|
||||
// Get the headers
|
||||
if (!isset($result) || empty($result->incomplete)) {
|
||||
$result_h = $rcmail->storage->list_messages($mbox, 1, $sort_column, $sort_order);
|
||||
}
|
||||
|
||||
// Make sure we got the headers
|
||||
if (!empty($result_h)) {
|
||||
$count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');
|
||||
|
||||
self::js_message_list($result_h, false);
|
||||
|
||||
if ($search_str) {
|
||||
$all_count = $rcmail->storage->count(null, 'ALL');
|
||||
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $all_count]);
|
||||
}
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
// we need it for flag updates in check-recent
|
||||
if ($mbox !== null) {
|
||||
$data = $rcmail->storage->folder_data($mbox);
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle IMAP errors (e.g. #1486905)
|
||||
else if ($err_code = $rcmail->storage->get_error_code()) {
|
||||
$count = 0;
|
||||
self::display_server_error();
|
||||
}
|
||||
// advice the client to re-send the (cross-folder) search request
|
||||
else if (!empty($result) && !empty($result->incomplete)) {
|
||||
$count = 0; // keep UI locked
|
||||
$rcmail->output->command('continue_search', $search_request);
|
||||
}
|
||||
else {
|
||||
$count = 0;
|
||||
|
||||
$rcmail->output->show_message('searchnomatch', 'notice');
|
||||
$rcmail->output->set_env('multifolder_listing', isset($result) ? !empty($result->multi) : false);
|
||||
|
||||
if (isset($result) && !empty($result->multi) && $scope == 'all') {
|
||||
$rcmail->output->command('select_folder', '');
|
||||
}
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('search_request', $search_str ? $search_request : '');
|
||||
$rcmail->output->set_env('search_filter', $_SESSION['search_filter']);
|
||||
$rcmail->output->set_env('messagecount', $count);
|
||||
$rcmail->output->set_env('pagecount', ceil($count / $rcmail->storage->get_pagesize()));
|
||||
$rcmail->output->set_env('exists', $mbox === null ? 0 : $rcmail->storage->count($mbox, 'EXISTS'));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count, 1), $mbox);
|
||||
|
||||
self::list_pagetitle();
|
||||
|
||||
// update unseen messages count
|
||||
if ($search_str === '') {
|
||||
self::send_unread_count($mbox, false, empty($result_h) ? 0 : null);
|
||||
}
|
||||
|
||||
if (isset($result) && empty($result->incomplete)) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, !empty($result->multi) ? 'INBOX' : $mbox));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates BEFORE/SINCE search criteria from the specified interval
|
||||
* Interval can be: 1W, 1M, 1Y, -1W, -1M, -1Y
|
||||
*/
|
||||
public static function search_interval_criteria($interval)
|
||||
{
|
||||
if (empty($interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($interval[0] == '-') {
|
||||
$search = 'BEFORE';
|
||||
$interval = substr($interval, 1);
|
||||
}
|
||||
else {
|
||||
$search = 'SINCE';
|
||||
}
|
||||
|
||||
$date = new DateTime('now');
|
||||
$interval = new DateInterval('P' . $interval);
|
||||
|
||||
$date->sub($interval);
|
||||
|
||||
return $search . ' ' . $date->format('j-M-Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse search input.
|
||||
*
|
||||
* @param string $str Search string
|
||||
* @param string $headers Comma-separated list of headers/fields to search in
|
||||
* @param string $scope Search scope (all | base | sub)
|
||||
* @param string $mbox Folder name
|
||||
*
|
||||
* @return array Search criteria (1st element) and search value (2nd element)
|
||||
*/
|
||||
public static function search_input($str, $headers, $scope, $mbox)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$subject = [];
|
||||
$srch = null;
|
||||
$supported = ['subject', 'from', 'to', 'cc', 'bcc'];
|
||||
|
||||
// Check the search string for type of search
|
||||
if (preg_match("/^(from|to|reply-to|cc|bcc|subject):.*/i", $str, $m)) {
|
||||
list(, $srch) = explode(":", $str);
|
||||
$subject[$m[1]] = 'HEADER ' . strtoupper($m[1]);
|
||||
}
|
||||
else if (preg_match("/^body:.*/i", $str)) {
|
||||
list(, $srch) = explode(":", $str);
|
||||
$subject['body'] = 'BODY';
|
||||
}
|
||||
else if (strlen(trim($str))) {
|
||||
if ($headers) {
|
||||
foreach (explode(',', $headers) as $header) {
|
||||
switch ($header) {
|
||||
case 'text':
|
||||
// #1488208: get rid of other headers when searching by "TEXT"
|
||||
$subject = ['text' => 'TEXT'];
|
||||
break 2;
|
||||
case 'body':
|
||||
$subject['body'] = 'BODY';
|
||||
break;
|
||||
case 'replyto':
|
||||
case 'reply-to':
|
||||
$subject['reply-to'] = 'HEADER REPLY-TO';
|
||||
$subject['mail-reply-to'] = 'HEADER MAIL-REPLY-TO';
|
||||
break;
|
||||
case 'followupto':
|
||||
case 'followup-to':
|
||||
$subject['followup-to'] = 'HEADER FOLLOWUP-TO';
|
||||
$subject['mail-followup-to'] = 'HEADER MAIL-FOLLOWUP-TO';
|
||||
break;
|
||||
default:
|
||||
if (in_array_nocase($header, $supported)) {
|
||||
$subject[$header] = 'HEADER ' . strtoupper($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save search modifiers for the current folder to user prefs
|
||||
if ($scope != 'all') {
|
||||
$search_mods = self::search_mods();
|
||||
$search_mods_value = array_fill_keys(array_keys($subject), 1);
|
||||
|
||||
if (!isset($search_mods[$mbox]) || $search_mods[$mbox] != $search_mods_value) {
|
||||
$search_mods[$mbox] = $search_mods_value;
|
||||
$rcmail->user->save_prefs(['search_mods' => $search_mods]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// search in subject by default
|
||||
$subject['subject'] = 'HEADER SUBJECT';
|
||||
}
|
||||
}
|
||||
|
||||
return [$subject, isset($srch) ? trim($srch) : trim($str)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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: |
|
||||
| Search contacts from the address book widget |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_search_contacts extends rcmail_action_mail_list_contacts
|
||||
{
|
||||
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();
|
||||
$search = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GPC, true);
|
||||
$sources = $rcmail->get_address_sources();
|
||||
$search_mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$records = [];
|
||||
$search_set = [];
|
||||
$jsresult = [];
|
||||
$search_mode |= rcube_addressbook::SEARCH_GROUPS;
|
||||
|
||||
foreach ($sources as $s) {
|
||||
$source = $rcmail->get_address_book($s['id']);
|
||||
$source->set_page(1);
|
||||
$source->set_pagesize(9999);
|
||||
|
||||
// list matching groups of this source
|
||||
if ($source->groups) {
|
||||
$jsresult += self::compose_contact_groups($source, $s['id'], $search, $search_mode);
|
||||
}
|
||||
|
||||
// get contacts count
|
||||
$result = $source->search($afields, $search, $search_mode, true, true, 'email');
|
||||
|
||||
if (!$result->count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($row = $result->next()) {
|
||||
$row['sourceid'] = $s['id'];
|
||||
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
|
||||
$records[$key] = $row;
|
||||
}
|
||||
|
||||
$search_set[$s['id']] = $source->get_search_set();
|
||||
unset($result);
|
||||
}
|
||||
|
||||
$group_count = count($jsresult);
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$result = new rcube_result_set($count);
|
||||
|
||||
// select the requested page
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $result->first, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
|
||||
if (!empty($result) && $result->count > 0) {
|
||||
// create javascript list
|
||||
while ($row = $result->next()) {
|
||||
$name = rcube_addressbook::compose_list_name($row);
|
||||
$is_group = isset($row['_type']) && $row['_type'] == 'group';
|
||||
$classname = $is_group ? 'group' : 'person';
|
||||
$keyname = $is_group ? 'contactgroup' : 'contact';
|
||||
|
||||
// add record for every email address of the contact
|
||||
// (same as in list_contacts.inc)
|
||||
$emails = rcube_addressbook::get_col_values('email', $row, true);
|
||||
|
||||
foreach ($emails as $i => $email) {
|
||||
$row_id = $row['sourceid'].'-'.$row['ID'].'-'.$i;
|
||||
|
||||
$jsresult[$row_id] = format_email_recipient($email, $name);
|
||||
|
||||
$title = rcube_addressbook::compose_search_name($row, $email, $name);
|
||||
$link_content = rcube::Q($name ?: $email);
|
||||
if ($name && count($emails) > 1) {
|
||||
$link_content .= ' ' . html::span('email', rcube::Q($email));
|
||||
}
|
||||
$link = html::a(['title' => $title], $link_content);
|
||||
|
||||
$rcmail->output->command('add_contact_row', $row_id, [$keyname => $link], $classname);
|
||||
}
|
||||
}
|
||||
|
||||
// search request ID
|
||||
$search_request = md5('composeaddr' . $search);
|
||||
|
||||
// save search settings in session
|
||||
$_SESSION['contact_search'][$search_request] = $search_set;
|
||||
$_SESSION['contact_search_params'] = ['id' => $search_request, 'data' => [$afields, $search]];
|
||||
|
||||
$rcmail->output->show_message('contactsearchsuccessful', 'confirmation', ['nr' => $result->count]);
|
||||
|
||||
$rcmail->output->set_env('search_request', $search_request);
|
||||
$rcmail->output->set_env('source', '');
|
||||
$rcmail->output->command('unselect_directory');
|
||||
}
|
||||
else if (!$group_count) {
|
||||
$rcmail->output->show_message('nocontactsfound', 'notice');
|
||||
}
|
||||
|
||||
// update env
|
||||
$rcmail->output->set_env('contactdata', $jsresult);
|
||||
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
|
||||
$rcmail->output->command('set_page_buttons');
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
<?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 and send it or store as draft |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_send extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// remove all scripts and act as called in frame
|
||||
$rcmail->output->reset();
|
||||
$rcmail->output->framed = true;
|
||||
|
||||
$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
|
||||
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
|
||||
|
||||
// Sanity checks
|
||||
if (!isset($COMPOSE['id'])) {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Invalid compose ID"
|
||||
], true, false
|
||||
);
|
||||
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$saveonly = !empty($_GET['_saveonly']);
|
||||
$savedraft = !empty($_POST['_draft']) && !$saveonly;
|
||||
$SENDMAIL = new rcmail_sendmail($COMPOSE, [
|
||||
'sendmail' => true,
|
||||
'saveonly' => $saveonly,
|
||||
'savedraft' => $savedraft,
|
||||
'error_handler' => function(...$args) use ($rcmail) {
|
||||
call_user_func_array([$rcmail->output, 'show_message'], $args);
|
||||
$rcmail->output->send('iframe');
|
||||
},
|
||||
'keepformatting' => !empty($_POST['_keepformatting']),
|
||||
]);
|
||||
|
||||
if (!isset($COMPOSE['attachments'])) {
|
||||
$COMPOSE['attachments'] = [];
|
||||
}
|
||||
|
||||
// Collect input for message headers
|
||||
$headers = $SENDMAIL->headers_input();
|
||||
|
||||
$COMPOSE['param']['message-id'] = $headers['Message-ID'];
|
||||
|
||||
$message_id = $headers['Message-ID'];
|
||||
$message_charset = $SENDMAIL->options['charset'];
|
||||
$message_body = rcube_utils::get_input_string('_message', rcube_utils::INPUT_POST, true, $message_charset);
|
||||
$isHtml = (bool) rcube_utils::get_input_string('_is_html', rcube_utils::INPUT_POST);
|
||||
|
||||
// Reset message body and attachments in Mailvelope mode
|
||||
if (isset($_POST['_pgpmime'])) {
|
||||
$pgp_mime = rcube_utils::get_input_string('_pgpmime', rcube_utils::INPUT_POST);
|
||||
$isHtml = false;
|
||||
$message_body = '';
|
||||
|
||||
// clear unencrypted attachments
|
||||
if (!empty($COMPOSE['attachments'])) {
|
||||
foreach ((array) $COMPOSE['attachments'] as $attach) {
|
||||
$rcmail->plugins->exec_hook('attachment_delete', $attach);
|
||||
}
|
||||
}
|
||||
|
||||
$COMPOSE['attachments'] = [];
|
||||
}
|
||||
|
||||
if ($isHtml) {
|
||||
$bstyle = [];
|
||||
|
||||
if ($font_size = $rcmail->config->get('default_font_size')) {
|
||||
$bstyle[] = 'font-size: ' . $font_size;
|
||||
}
|
||||
if ($font_family = $rcmail->config->get('default_font')) {
|
||||
$bstyle[] = 'font-family: ' . self::font_defs($font_family);
|
||||
}
|
||||
|
||||
// append doctype and html/body wrappers
|
||||
$bstyle = !empty($bstyle) ? (" style='" . implode('; ', $bstyle) . "'") : '';
|
||||
$message_body = '<html><head>'
|
||||
. '<meta http-equiv="Content-Type" content="text/html; charset='
|
||||
. ($message_charset ?: RCUBE_CHARSET) . '" /></head>'
|
||||
. "<body" . $bstyle . ">\r\n" . $message_body;
|
||||
}
|
||||
|
||||
if (!$savedraft) {
|
||||
if ($isHtml) {
|
||||
$b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
|
||||
$pre_style = 'margin: 0; padding: 0; font-family: monospace';
|
||||
|
||||
$message_body = preg_replace(
|
||||
[
|
||||
// remove empty signature div
|
||||
'/<div id="_rc_sig">( )?<\/div>[\s\r\n]*$/',
|
||||
// replace signature's div ID (#6073)
|
||||
'/ id="_rc_sig"/',
|
||||
// add inline css for blockquotes and container
|
||||
'/<blockquote>/',
|
||||
'/<div class="pre">/',
|
||||
// convert TinyMCE's new-line sequences (#1490463)
|
||||
'/<p> <\/p>/',
|
||||
],
|
||||
[
|
||||
'',
|
||||
' id="signature"',
|
||||
'<blockquote type="cite" style="'.$b_style.'">',
|
||||
'<div class="pre" style="'.$pre_style.'">',
|
||||
'<p><br /></p>',
|
||||
],
|
||||
$message_body
|
||||
);
|
||||
|
||||
rcube_utils::preg_error([
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not format HTML!"
|
||||
], true);
|
||||
}
|
||||
|
||||
// Check spelling before send
|
||||
if (
|
||||
$rcmail->config->get('spellcheck_before_send')
|
||||
&& $rcmail->config->get('enable_spellcheck')
|
||||
&& empty($COMPOSE['spell_checked'])
|
||||
&& !empty($message_body)
|
||||
) {
|
||||
$language = rcube_utils::get_input_string('_lang', rcube_utils::INPUT_GPC);
|
||||
$message_body = str_replace("\r\n", "\n", $message_body);
|
||||
$spellchecker = new rcube_spellchecker($language);
|
||||
$spell_result = $spellchecker->check($message_body, $isHtml);
|
||||
|
||||
if ($error = $spellchecker->error()) {
|
||||
rcube::raise_error([
|
||||
'code' => 500, 'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Spellcheck error: " . $error
|
||||
],
|
||||
true, false
|
||||
);
|
||||
}
|
||||
else {
|
||||
$COMPOSE['spell_checked'] = true;
|
||||
|
||||
if (!$spell_result) {
|
||||
if ($isHtml) {
|
||||
$result['words'] = $spellchecker->get();
|
||||
$result['dictionary'] = (bool) $rcmail->config->get('spellcheck_dictionary');
|
||||
}
|
||||
else {
|
||||
$result = $spellchecker->get_xml();
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('mispellingsfound', 'error');
|
||||
$rcmail->output->command('spellcheck_resume', $result);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generic footer for all messages
|
||||
if ($footer = $SENDMAIL->generic_message_footer($isHtml)) {
|
||||
$message_body .= "\r\n" . $footer;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isHtml) {
|
||||
$message_body .= "\r\n</body></html>\r\n";
|
||||
}
|
||||
|
||||
// sort attachments to make sure the order is the same as in the UI (#1488423)
|
||||
if ($files = rcube_utils::get_input_string('_attachments', rcube_utils::INPUT_POST)) {
|
||||
$files = explode(',', $files);
|
||||
$files = array_flip($files);
|
||||
foreach ($files as $idx => $val) {
|
||||
if (!empty($COMPOSE['attachments'][$idx])) {
|
||||
$files[$idx] = $COMPOSE['attachments'][$idx];
|
||||
unset($COMPOSE['attachments'][$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$COMPOSE['attachments'] = array_merge(array_filter($files), (array) $COMPOSE['attachments']);
|
||||
}
|
||||
|
||||
// Since we can handle big messages with disk usage, we need more time to work
|
||||
@set_time_limit(360);
|
||||
|
||||
// create PEAR::Mail_mime instance, set headers, body and params
|
||||
$MAIL_MIME = $SENDMAIL->create_message($headers, $message_body, $isHtml, $COMPOSE['attachments']);
|
||||
|
||||
// add stored attachments, if any
|
||||
if (is_array($COMPOSE['attachments'])) {
|
||||
self::add_attachments($SENDMAIL, $MAIL_MIME, $COMPOSE['attachments'], $isHtml);
|
||||
}
|
||||
|
||||
// compose PGP/Mime message
|
||||
if (!empty($pgp_mime)) {
|
||||
$MAIL_MIME->addAttachment(new Mail_mimePart('Version: 1', [
|
||||
'content_type' => 'application/pgp-encrypted',
|
||||
'description' => 'PGP/MIME version identification',
|
||||
]));
|
||||
|
||||
$MAIL_MIME->addAttachment(new Mail_mimePart($pgp_mime, [
|
||||
'content_type' => 'application/octet-stream',
|
||||
'filename' => 'encrypted.asc',
|
||||
'disposition' => 'inline',
|
||||
]));
|
||||
|
||||
$MAIL_MIME->setContentType('multipart/encrypted', ['protocol' => 'application/pgp-encrypted']);
|
||||
$MAIL_MIME->setParam('preamble', 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)');
|
||||
}
|
||||
|
||||
// This hook allows to modify the message before send or save action
|
||||
$plugin = $rcmail->plugins->exec_hook('message_ready', ['message' => $MAIL_MIME]);
|
||||
$MAIL_MIME = $plugin['message'];
|
||||
|
||||
// Deliver the message over SMTP
|
||||
if (!$savedraft && !$saveonly) {
|
||||
$sent = $SENDMAIL->deliver_message($MAIL_MIME);
|
||||
}
|
||||
|
||||
// Save the message in Drafts/Sent
|
||||
$saved = $SENDMAIL->save_message($MAIL_MIME);
|
||||
|
||||
// raise error if saving failed
|
||||
if (!$saved && $savedraft) {
|
||||
self::display_server_error('errorsaving');
|
||||
// start the auto-save timer again
|
||||
$rcmail->output->command('auto_save_start');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$store_target = $SENDMAIL->options['store_target'];
|
||||
$store_folder = $SENDMAIL->options['store_folder'];
|
||||
|
||||
// delete previous saved draft
|
||||
$drafts_mbox = $rcmail->config->get('drafts_mbox');
|
||||
$old_id = rcube_utils::get_input_string('_draft_saveid', rcube_utils::INPUT_POST);
|
||||
|
||||
if ($old_id && (!empty($sent) || $saved)) {
|
||||
$deleted = $rcmail->storage->delete_message($old_id, $drafts_mbox);
|
||||
|
||||
// raise error if deletion of old draft failed
|
||||
if (!$deleted) {
|
||||
rcube::raise_error([
|
||||
'code' => 800,
|
||||
'type' => 'imap',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Could not delete message from $drafts_mbox"
|
||||
],
|
||||
true, false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($savedraft) {
|
||||
// remember new draft-uid ($saved could be an UID or true/false here)
|
||||
if ($saved && is_bool($saved)) {
|
||||
$index = $rcmail->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
|
||||
$saved = $index->max();
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$plugin = $rcmail->plugins->exec_hook('message_draftsaved', [
|
||||
'msgid' => $message_id,
|
||||
'uid' => $saved,
|
||||
'folder' => $store_target
|
||||
]);
|
||||
|
||||
// display success
|
||||
$rcmail->output->show_message(!empty($plugin['message']) ? $plugin['message'] : 'messagesaved', 'confirmation');
|
||||
|
||||
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
|
||||
$COMPOSE['param']['draft_uid'] = $plugin['uid'];
|
||||
$rcmail->output->command('set_draft_id', $plugin['uid']);
|
||||
$rcmail->output->command('compose_field_hash', true);
|
||||
}
|
||||
|
||||
// start the auto-save timer again
|
||||
$rcmail->output->command('auto_save_start');
|
||||
}
|
||||
else {
|
||||
// Collect folders which could contain the composed message,
|
||||
// we'll refresh the list if currently opened folder is one of them (#1490238)
|
||||
$folders = [];
|
||||
$save_error = false;
|
||||
|
||||
if (!$saveonly) {
|
||||
if (in_array($COMPOSE['mode'], ['reply', 'forward', 'draft'])) {
|
||||
$folders[] = $COMPOSE['mailbox'];
|
||||
}
|
||||
if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
|
||||
$folders[] = $drafts_mbox;
|
||||
}
|
||||
}
|
||||
|
||||
if ($store_folder && !$saved) {
|
||||
$params = $saveonly ? null : ['prefix' => true];
|
||||
self::display_server_error('errorsavingsent', null, null, $params);
|
||||
|
||||
if ($saveonly) {
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$save_error = true;
|
||||
}
|
||||
else {
|
||||
$rcmail->plugins->exec_hook('attachments_cleanup', ['group' => $COMPOSE_ID]);
|
||||
$rcmail->session->remove('compose_data_' . $COMPOSE_ID);
|
||||
$_SESSION['last_compose_session'] = $COMPOSE_ID;
|
||||
|
||||
$rcmail->output->command('remove_compose_data', $COMPOSE_ID);
|
||||
|
||||
if ($store_folder) {
|
||||
$folders[] = $store_target;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = $rcmail->gettext($saveonly ? 'successfullysaved' : 'messagesent');
|
||||
|
||||
$rcmail->output->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
|
||||
}
|
||||
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function add_attachments($SENDMAIL, $message, $attachments, $isHtml)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
foreach ($attachments as $id => $attachment) {
|
||||
// This hook retrieves the attachment contents from the file storage backend
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_get', $attachment);
|
||||
$is_inline = false;
|
||||
$dispurl = null;
|
||||
|
||||
if ($isHtml) {
|
||||
$dispurl = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
|
||||
$message_body = $message->getHTMLBody();
|
||||
$is_inline = preg_match($dispurl, $message_body);
|
||||
}
|
||||
|
||||
$ctype = isset($attachment['mimetype']) ? $attachment['mimetype'] : '';
|
||||
$ctype = str_replace('image/pjpeg', 'image/jpeg', $ctype); // #1484914
|
||||
|
||||
// inline image
|
||||
if ($is_inline) {
|
||||
// Mail_Mime does not support many inline attachments with the same name (#1489406)
|
||||
// we'll generate cid: urls here to workaround this
|
||||
$cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
|
||||
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $SENDMAIL->options['from'], $matches)) {
|
||||
$cid .= $matches[1];
|
||||
}
|
||||
else {
|
||||
$cid .= '@localhost';
|
||||
}
|
||||
|
||||
if ($dispurl && !empty($message_body)) {
|
||||
$message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
|
||||
|
||||
rcube_utils::preg_error([
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not replace an image reference!"
|
||||
], true
|
||||
);
|
||||
|
||||
$message->setHTMLBody($message_body);
|
||||
}
|
||||
|
||||
if (!empty($attachment['data'])) {
|
||||
$message->addHTMLImage($attachment['data'], $ctype, $attachment['name'], false, $cid);
|
||||
}
|
||||
else {
|
||||
$message->addHTMLImage($attachment['path'], $ctype, $attachment['name'], true, $cid);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$file = !empty($attachment['data']) ? $attachment['data'] : $attachment['path'];
|
||||
$folding = (int) $rcmail->config->get('mime_param_folding');
|
||||
|
||||
$message->addAttachment($file,
|
||||
$ctype,
|
||||
$attachment['name'],
|
||||
empty($attachment['data']),
|
||||
$ctype == 'message/rfc822' ? '8bit' : 'base64',
|
||||
'attachment',
|
||||
$attachment['charset'] ?? null,
|
||||
'', '',
|
||||
$folding ? 'quoted-printable' : null,
|
||||
$folding == 2 ? 'quoted-printable' : null,
|
||||
'', RCUBE_CHARSET
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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: |
|
||||
| Send a message disposition notification for a specific mail |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_sendmdn extends rcmail_action
|
||||
{
|
||||
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();
|
||||
|
||||
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_POST)) {
|
||||
$sent = self::send_mdn($uid, $smtp_error);
|
||||
}
|
||||
|
||||
// show either confirm or error message
|
||||
if (!empty($sent)) {
|
||||
$rcmail->output->set_env('mdn_request', false);
|
||||
$rcmail->output->show_message('receiptsent', 'confirmation');
|
||||
}
|
||||
else if (!empty($smtp_error) && is_string($smtp_error)) {
|
||||
$rcmail->output->show_message($smtp_error, 'error');
|
||||
}
|
||||
else if (!empty($smtp_error) && !empty($smtp_error['label'])) {
|
||||
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('errorsendingreceipt', 'error');
|
||||
}
|
||||
|
||||
// Redirect to 'addcontact' action to save the sender address
|
||||
if (!empty($_POST['_save'])) {
|
||||
if ($_POST['_save'] == 5) {
|
||||
$_POST['_source'] = rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
}
|
||||
|
||||
$rcmail->action = 'addcontact';
|
||||
return;
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the MDN response
|
||||
*
|
||||
* @param mixed $message Original message object (rcube_message) or UID
|
||||
* @param array|string $smtp_error SMTP error array or (deprecated) string
|
||||
*
|
||||
* @return boolean Send status
|
||||
*/
|
||||
public static function send_mdn($message, &$smtp_error)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (!is_object($message) || !is_a($message, 'rcube_message')) {
|
||||
$message = new rcube_message($message);
|
||||
}
|
||||
|
||||
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
|
||||
($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
|
||||
) {
|
||||
$charset = $message->headers->charset;
|
||||
$identity = rcmail_sendmail::identity_select($message);
|
||||
$sender = format_email_recipient($identity['email'], $identity['name']);
|
||||
$recipient = array_first(rcube_mime::decode_address_list($message->headers->mdn_to, 1, true, $charset));
|
||||
$mailto = $recipient['mailto'];
|
||||
|
||||
$compose = new Mail_mime("\r\n");
|
||||
|
||||
$compose->setParam('text_encoding', 'quoted-printable');
|
||||
$compose->setParam('html_encoding', 'quoted-printable');
|
||||
$compose->setParam('head_encoding', 'quoted-printable');
|
||||
$compose->setParam('head_charset', RCUBE_CHARSET);
|
||||
$compose->setParam('html_charset', RCUBE_CHARSET);
|
||||
$compose->setParam('text_charset', RCUBE_CHARSET);
|
||||
|
||||
// compose headers array
|
||||
$headers = [
|
||||
'Date' => $rcmail->user_date(),
|
||||
'From' => $sender,
|
||||
'To' => $message->headers->mdn_to,
|
||||
'Subject' => $rcmail->gettext('receiptread') . ': ' . $message->subject,
|
||||
'Message-ID' => $rcmail->gen_message_id($identity['email']),
|
||||
'X-Sender' => $identity['email'],
|
||||
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
|
||||
'In-Reply-To' => $message->headers->messageID,
|
||||
];
|
||||
|
||||
$report = "Final-Recipient: rfc822; {$identity['email']}\r\n"
|
||||
. "Original-Message-ID: {$message->headers->messageID}\r\n"
|
||||
. "Disposition: manual-action/MDN-sent-manually; displayed\r\n";
|
||||
|
||||
if ($message->headers->to) {
|
||||
$report .= "Original-Recipient: {$message->headers->to}\r\n";
|
||||
}
|
||||
|
||||
if ($agent = $rcmail->config->get('useragent')) {
|
||||
$headers['User-Agent'] = $agent;
|
||||
$report .= "Reporting-UA: $agent\r\n";
|
||||
}
|
||||
|
||||
$to = rcube_mime::decode_mime_string($message->headers->to, $charset);
|
||||
$date = $rcmail->format_date($message->headers->date, $rcmail->config->get('date_long'));
|
||||
$body = $rcmail->gettext("yourmessage") . "\r\n\r\n" .
|
||||
"\t" . $rcmail->gettext("to") . ": {$to}\r\n" .
|
||||
"\t" . $rcmail->gettext("subject") . ": {$message->subject}\r\n" .
|
||||
"\t" . $rcmail->gettext("date") . ": {$date}\r\n" .
|
||||
"\r\n" . $rcmail->gettext("receiptnote");
|
||||
|
||||
$compose->headers(array_filter($headers));
|
||||
$compose->setContentType('multipart/report', ['report-type'=> 'disposition-notification']);
|
||||
$compose->setTXTBody(rcube_mime::wordwrap($body, 75, "\r\n"));
|
||||
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
|
||||
|
||||
// SMTP options
|
||||
$options = ['mdn_use_from' => (bool) $rcmail->config->get('mdn_use_from')];
|
||||
|
||||
$sent = $rcmail->deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options, true);
|
||||
|
||||
if ($sent) {
|
||||
$rcmail->storage->set_flag($message->uid, 'MDNSENT');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
<?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: |
|
||||
| Display a mail message similar as a usual mail application does |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_show extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $MESSAGE;
|
||||
protected static $CLIENT_MIMETYPES = [];
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
self::$PRINT_MODE = $rcmail->action == 'print';
|
||||
|
||||
// Read browser capabilities and store them in session
|
||||
if ($caps = rcube_utils::get_input_string('_caps', rcube_utils::INPUT_GET)) {
|
||||
$browser_caps = [];
|
||||
foreach (explode(',', $caps) as $cap) {
|
||||
$cap = explode('=', $cap);
|
||||
$browser_caps[$cap[0]] = $cap[1];
|
||||
}
|
||||
|
||||
$_SESSION['browser_caps'] = $browser_caps;
|
||||
}
|
||||
|
||||
$msg_id = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
$uid = preg_replace('/\.[0-9.]+$/', '', $msg_id);
|
||||
$mbox_name = $rcmail->storage->get_folder();
|
||||
|
||||
// similar code as in program/steps/mail/get.inc
|
||||
if ($uid) {
|
||||
// set message format (need to be done before rcube_message construction)
|
||||
if (!empty($_GET['_format'])) {
|
||||
$prefer_html = $_GET['_format'] == 'html';
|
||||
$rcmail->config->set('prefer_html', $prefer_html);
|
||||
$_SESSION['msg_formats'][$mbox_name.':'.$uid] = $prefer_html;
|
||||
}
|
||||
else if (isset($_SESSION['msg_formats'][$mbox_name.':'.$uid])) {
|
||||
$rcmail->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$uid]);
|
||||
}
|
||||
|
||||
$MESSAGE = new rcube_message($msg_id, $mbox_name, !empty($_GET['_safe']));
|
||||
|
||||
self::$MESSAGE = $MESSAGE;
|
||||
|
||||
// if message not found (wrong UID)...
|
||||
if (empty($MESSAGE->headers)) {
|
||||
self::message_error();
|
||||
}
|
||||
|
||||
self::$CLIENT_MIMETYPES = self::supported_mimetypes();
|
||||
|
||||
// show images?
|
||||
self::check_safe($MESSAGE);
|
||||
|
||||
// set message charset as default
|
||||
if (!empty($MESSAGE->headers->charset)) {
|
||||
$rcmail->storage->set_charset($MESSAGE->headers->charset);
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['writeable_abook'])) {
|
||||
$_SESSION['writeable_abook'] = $rcmail->get_address_sources(true) ? true : false;
|
||||
}
|
||||
|
||||
$rcmail->output->set_pagetitle(abbreviate_string($MESSAGE->subject, 128, '...', true));
|
||||
|
||||
// set environment
|
||||
$rcmail->output->set_env('uid', $msg_id);
|
||||
$rcmail->output->set_env('safemode', $MESSAGE->is_safe);
|
||||
$rcmail->output->set_env('message_context', $MESSAGE->context);
|
||||
$rcmail->output->set_env('message_flags', array_keys(array_change_key_case((array) $MESSAGE->headers->flags)));
|
||||
$rcmail->output->set_env('sender', !empty($MESSAGE->sender) ? $MESSAGE->sender['string'] : '');
|
||||
$rcmail->output->set_env('mailbox', $mbox_name);
|
||||
$rcmail->output->set_env('username', $rcmail->get_user_name());
|
||||
$rcmail->output->set_env('permaurl', $rcmail->url(['_action' => 'show', '_uid' => $msg_id, '_mbox' => $mbox_name]));
|
||||
$rcmail->output->set_env('has_writeable_addressbook', $_SESSION['writeable_abook']);
|
||||
$rcmail->output->set_env('delimiter', $rcmail->storage->get_hierarchy_delimiter());
|
||||
$rcmail->output->set_env('mimetypes', self::$CLIENT_MIMETYPES);
|
||||
|
||||
if ($MESSAGE->headers->get('list-post', false)) {
|
||||
$rcmail->output->set_env('list_post', true);
|
||||
}
|
||||
|
||||
// set configuration
|
||||
self::set_env_config(['delete_junk', 'flag_for_deletion', 'read_when_deleted',
|
||||
'skip_deleted', 'display_next', 'forward_attachment', 'mailvelope_main_keyring']);
|
||||
|
||||
// set special folders
|
||||
foreach (['drafts', 'trash', 'junk'] as $mbox) {
|
||||
if ($folder = $rcmail->config->get($mbox . '_mbox')) {
|
||||
$rcmail->output->set_env($mbox . '_mailbox', $folder);
|
||||
}
|
||||
}
|
||||
|
||||
if ($MESSAGE->has_html_part()) {
|
||||
$prefer_html = $rcmail->config->get('prefer_html');
|
||||
$rcmail->output->set_env('optional_format', $prefer_html ? 'text' : 'html');
|
||||
}
|
||||
|
||||
$rcmail->output->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
|
||||
'movingmessage', 'deletingmessage', 'markingmessage', 'replyall', 'replylist',
|
||||
'bounce', 'bouncemsg', 'sendingmessage');
|
||||
|
||||
// check for unset disposition notification
|
||||
self::mdn_request_handler($MESSAGE);
|
||||
|
||||
if (empty($MESSAGE->headers->flags['SEEN']) && $MESSAGE->context === null) {
|
||||
$v = intval($rcmail->config->get('mail_read_time'));
|
||||
if ($v > 0) {
|
||||
$rcmail->output->set_env('mail_read_time', $v);
|
||||
}
|
||||
else if ($v == 0) {
|
||||
$rcmail->output->command('set_unread_message', $MESSAGE->uid, $mbox_name);
|
||||
$rcmail->plugins->exec_hook('message_read', [
|
||||
'uid' => $MESSAGE->uid,
|
||||
'mailbox' => $mbox_name,
|
||||
'message' => $MESSAGE,
|
||||
]);
|
||||
|
||||
$set_seen_flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'mailboxname' => [$this, 'mailbox_name_display'],
|
||||
'messageattachments' => [$this, 'message_attachments'],
|
||||
'messageobjects' => [$this, 'message_objects'],
|
||||
'messagesummary' => [$this, 'message_summary'],
|
||||
'messageheaders' => [$this, 'message_headers'],
|
||||
'messagefullheaders' => [$this, 'message_full_headers'],
|
||||
'messagebody' => [$this, 'message_body'],
|
||||
'contactphoto' => [$this, 'message_contactphoto'],
|
||||
]);
|
||||
|
||||
if ($rcmail->action == 'print' && $rcmail->output->template_exists('messageprint')) {
|
||||
$rcmail->output->send('messageprint', false);
|
||||
}
|
||||
else if ($rcmail->action == 'preview' && $rcmail->output->template_exists('messagepreview')) {
|
||||
$rcmail->output->send('messagepreview', false);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->send('message', false);
|
||||
}
|
||||
|
||||
// mark message as read
|
||||
if (!empty($set_seen_flag)) {
|
||||
if ($rcmail->storage->set_flag(self::$MESSAGE->uid, 'SEEN', $mbox_name)) {
|
||||
if ($count = self::get_unseen_count($mbox_name)) {
|
||||
self::set_unseen_count($mbox_name, $count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'messageattachments'.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message attachments list
|
||||
*/
|
||||
public static function message_attachments($attrib)
|
||||
{
|
||||
if (empty(self::$MESSAGE->attachments)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$out =
|
||||
$ol = '';
|
||||
$attachments = [];
|
||||
|
||||
foreach (self::$MESSAGE->attachments as $attach_prop) {
|
||||
$filename = self::attachment_name($attach_prop, true);
|
||||
$filesize = self::message_part_size($attach_prop);
|
||||
$mimetype = rcube_mime::fix_mimetype($attach_prop->mimetype);
|
||||
$class = rcube_utils::file2class($mimetype, $filename);
|
||||
$id = 'attach' . $attach_prop->mime_id;
|
||||
|
||||
if ($mimetype == 'application/octet-stream' && ($type = rcube_mime::file_ext_type($filename))) {
|
||||
$mimetype = $type;
|
||||
}
|
||||
|
||||
// Skip inline images
|
||||
if (strpos($mimetype, 'image/') === 0 && !self::is_attachment(self::$MESSAGE, $attach_prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($attrib['maxlength']) && mb_strlen($filename) > $attrib['maxlength']) {
|
||||
$title = $filename;
|
||||
$filename = abbreviate_string($filename, $attrib['maxlength']);
|
||||
}
|
||||
else {
|
||||
$title = '';
|
||||
}
|
||||
|
||||
$item = html::span('attachment-name', rcube::Q($filename))
|
||||
. html::span('attachment-size', '(' . rcube::Q($filesize) . ')');
|
||||
|
||||
$li_class = $class;
|
||||
|
||||
if (!self::$PRINT_MODE) {
|
||||
$link_attrs = [
|
||||
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
|
||||
'onclick' => sprintf('%s.command(\'load-attachment\',\'%s\',this); return false',
|
||||
rcmail_output::JS_OBJECT_NAME, $attach_prop->mime_id),
|
||||
'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
|
||||
'title' => $title,
|
||||
'class' => 'filename',
|
||||
];
|
||||
|
||||
if ($mimetype != 'message/rfc822' && empty($attach_prop->size)) {
|
||||
$li_class .= ' no-menu';
|
||||
$link_attrs['onclick'] = sprintf('%s.alert_dialog(%s.get_label(\'emptyattachment\')); return false',
|
||||
rcmail_output::JS_OBJECT_NAME, rcmail_output::JS_OBJECT_NAME);
|
||||
$rcmail->output->add_label('emptyattachment');
|
||||
}
|
||||
|
||||
$item = html::a($link_attrs, $item);
|
||||
$attachments[$attach_prop->mime_id] = $mimetype;
|
||||
}
|
||||
|
||||
$ol .= html::tag('li', ['class' => $li_class, 'id' => $id], $item);
|
||||
}
|
||||
|
||||
$out = html::tag('ul', $attrib, $ol, html::$common_attrib);
|
||||
|
||||
$rcmail->output->set_env('attachments', $attachments);
|
||||
$rcmail->output->add_gui_object('attachments', $attrib['id']);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function remote_objects_msg()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$attrib['id'] = 'remote-objects-message';
|
||||
$attrib['class'] = 'notice';
|
||||
$attrib['style'] = 'display: none';
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('blockedresources')));
|
||||
|
||||
$buttons = html::a([
|
||||
'href' => "#loadremote",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote')"
|
||||
],
|
||||
rcube::Q($rcmail->gettext('allow'))
|
||||
);
|
||||
|
||||
// add link to save sender in addressbook and reload message
|
||||
$show_images = $rcmail->config->get('show_images');
|
||||
if (!empty(self::$MESSAGE->sender['mailto']) && ($show_images == 1 || $show_images == 3)) {
|
||||
$arg = $show_images == 3 ? rcube_addressbook::TYPE_TRUSTED_SENDER : 'true';
|
||||
$buttons .= ' ' . html::a([
|
||||
'href' => "#loadremotealways",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote', $arg)",
|
||||
'style' => "white-space:nowrap"
|
||||
],
|
||||
rcube::Q($rcmail->gettext(['name' => 'alwaysallow', 'vars' => ['sender' => self::$MESSAGE->sender['mailto']]]))
|
||||
);
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('remoteobjectsmsg', $attrib['id']);
|
||||
|
||||
return html::div($attrib, $msg . ' ' . html::span('boxbuttons', $buttons));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning whenever a suspicious email address has been found in the message.
|
||||
*
|
||||
* @return string HTML content of the warning element
|
||||
*/
|
||||
public static function suspicious_content_warning()
|
||||
{
|
||||
if (empty(self::$SUSPICIOUS_EMAIL)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$attrib = [
|
||||
'id' => 'suspicious-content-message',
|
||||
'class' => 'notice',
|
||||
];
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('suspiciousemail')));
|
||||
|
||||
return html::div($attrib, $msg);
|
||||
}
|
||||
|
||||
public static function message_buttons()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$dbox = $rcmail->config->get('drafts_mbox');
|
||||
|
||||
// the message is not a draft
|
||||
if (!empty(self::$MESSAGE->context)
|
||||
|| (
|
||||
!empty(self::$MESSAGE->folder)
|
||||
&& (self::$MESSAGE->folder != $dbox && strpos(self::$MESSAGE->folder, $dbox.$delim) !== 0)
|
||||
)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attrib['id'] = 'message-buttons';
|
||||
$attrib['class'] = 'information notice';
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('isdraft')))
|
||||
. ' '
|
||||
. html::a([
|
||||
'href' => "#edit",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME.".command('edit')"
|
||||
],
|
||||
rcube::Q($rcmail->gettext('edit'))
|
||||
);
|
||||
|
||||
return html::div($attrib, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'messageobjects' that contains
|
||||
* warning/info boxes, buttons, etc. related to the displayed message.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message objects
|
||||
*/
|
||||
public static function message_objects($attrib)
|
||||
{
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'message-objects';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$content = [
|
||||
self::message_buttons(),
|
||||
self::remote_objects_msg(),
|
||||
self::suspicious_content_warning(),
|
||||
];
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_objects',
|
||||
['content' => $content, 'message' => self::$MESSAGE]);
|
||||
|
||||
$content = implode("\n", $plugin['content']);
|
||||
|
||||
return html::div($attrib, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'contactphoto'.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content for the IMG tag
|
||||
*/
|
||||
public static function message_contactphoto($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$error_handler = false;
|
||||
$placeholder = 'data:image/gif;base64,' . rcmail_output::BLANK_GIF;
|
||||
|
||||
if (!empty($attrib['placeholder'])) {
|
||||
$placeholder = $rcmail->output->abs_url($attrib['placeholder'], true);
|
||||
$placeholder = $rcmail->output->asset_url($placeholder);
|
||||
|
||||
// set error handler on <img>
|
||||
$error_handler = true;
|
||||
$attrib['onerror'] = "this.onerror = null; this.src = '$placeholder';";
|
||||
}
|
||||
|
||||
if (!empty(self::$MESSAGE->sender)) {
|
||||
$photo_img = $rcmail->url([
|
||||
'_task' => 'addressbook',
|
||||
'_action' => 'photo',
|
||||
'_email' => self::$MESSAGE->sender['mailto'],
|
||||
'_error' => $error_handler ? 1 : null,
|
||||
'_bgcolor' => $attrib['bg-color'] ?? null
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$photo_img = $placeholder;
|
||||
}
|
||||
|
||||
return html::img(['src' => $photo_img, 'alt' => $rcmail->gettext('contactphoto')] + $attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table with message headers
|
||||
*/
|
||||
public static function message_headers($attrib, $headers = null)
|
||||
{
|
||||
static $sa_attrib;
|
||||
|
||||
// keep header table attrib
|
||||
if (is_array($attrib) && !$sa_attrib && empty($attrib['valueof'])) {
|
||||
$sa_attrib = $attrib;
|
||||
}
|
||||
else if (!is_array($attrib) && is_array($sa_attrib)) {
|
||||
$attrib = $sa_attrib;
|
||||
}
|
||||
|
||||
if (!isset(self::$MESSAGE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// get associative array of headers object
|
||||
if (!$headers) {
|
||||
$headers_obj = self::$MESSAGE->headers;
|
||||
$headers = get_object_vars(self::$MESSAGE->headers);
|
||||
}
|
||||
else if (is_object($headers)) {
|
||||
$headers_obj = $headers;
|
||||
$headers = get_object_vars($headers_obj);
|
||||
}
|
||||
else {
|
||||
$headers_obj = rcube_message_header::from_array($headers);
|
||||
}
|
||||
|
||||
// show these headers
|
||||
$standard_headers = ['subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
|
||||
'mail-reply-to', 'mail-followup-to', 'date', 'priority'];
|
||||
$exclude_headers = !empty($attrib['exclude']) ? explode(',', $attrib['exclude']) : [];
|
||||
$output_headers = [];
|
||||
|
||||
$attr_max = $attrib['max'] ?? null;
|
||||
$attr_addicon = $attrib['addicon'] ?? null;
|
||||
$charset = !empty($headers['charset']) ? $headers['charset'] : null;
|
||||
|
||||
foreach ($standard_headers as $hkey) {
|
||||
$value = null;
|
||||
if (!empty($headers[$hkey])) {
|
||||
$value = $headers[$hkey];
|
||||
}
|
||||
else if (!empty($headers['others'][$hkey])) {
|
||||
$value = $headers['others'][$hkey];
|
||||
}
|
||||
else if (empty($attrib['valueof'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($hkey, $exclude_headers)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ishtml = false;
|
||||
$header_title = $rcmail->gettext(preg_replace('/(^mail-|-)/', '', $hkey));
|
||||
$header_value = null;
|
||||
|
||||
if ($hkey == 'date') {
|
||||
$header_value = $rcmail->format_date($value,
|
||||
self::$PRINT_MODE ? $rcmail->config->get('date_long', 'x') : null);
|
||||
}
|
||||
else if ($hkey == 'priority') {
|
||||
$header_value = html::span('prio' . $value, rcube::Q(self::localized_priority($value)));
|
||||
$ishtml = true;
|
||||
}
|
||||
else if ($hkey == 'replyto') {
|
||||
if ($value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'mail-reply-to') {
|
||||
if ((!isset($headers['replyto']) || $value != $headers['replyto']) && $value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'sender') {
|
||||
if ($value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'mail-followup-to') {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
else if (in_array($hkey, ['from', 'to', 'cc', 'bcc'])) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
else if ($hkey == 'subject' && empty($value)) {
|
||||
$header_value = $rcmail->gettext('nosubject');
|
||||
}
|
||||
else {
|
||||
$value = is_array($value) ? implode(' ', $value) : $value;
|
||||
$header_value = trim(rcube_mime::decode_header($value, $charset));
|
||||
}
|
||||
|
||||
if (empty($header_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output_headers[$hkey] = [
|
||||
'title' => $header_title,
|
||||
'value' => $header_value,
|
||||
'raw' => $value,
|
||||
'html' => $ishtml,
|
||||
];
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_headers_output', [
|
||||
'output' => $output_headers,
|
||||
'headers' => $headers_obj,
|
||||
'exclude' => $exclude_headers, // readonly
|
||||
'folder' => self::$MESSAGE->folder, // readonly
|
||||
'uid' => self::$MESSAGE->uid, // readonly
|
||||
]);
|
||||
|
||||
// single header value is requested
|
||||
if (!empty($attrib['valueof'])) {
|
||||
if (empty($plugin['output'][$attrib['valueof']])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$row = $plugin['output'][$attrib['valueof']];
|
||||
return !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
|
||||
}
|
||||
|
||||
// compose html table
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
foreach ($plugin['output'] as $hkey => $row) {
|
||||
$val = !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
|
||||
|
||||
$table->add(['class' => 'header-title'], rcube::SQ($row['title']));
|
||||
$table->add(['class' => 'header ' . $hkey], $val);
|
||||
}
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns element with "From|To <sender|recipient> on <date>"
|
||||
*/
|
||||
public static function message_summary($attrib)
|
||||
{
|
||||
if (!isset(self::$MESSAGE) || empty(self::$MESSAGE->headers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$header = self::$MESSAGE->context ? 'from' : self::message_list_smart_column_name();
|
||||
$label = 'shortheader' . $header;
|
||||
$date = $rcmail->format_date(self::$MESSAGE->headers->date, $rcmail->config->get('date_long', 'x'));
|
||||
$user = self::$MESSAGE->headers->$header;
|
||||
|
||||
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->cc)) {
|
||||
$user = self::$MESSAGE->headers->cc;
|
||||
}
|
||||
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->bcc)) {
|
||||
$user = self::$MESSAGE->headers->bcc;
|
||||
}
|
||||
|
||||
$vars[$header] = self::address_string($user, 1, true, $attrib['addicon'], self::$MESSAGE->headers->charset);
|
||||
$vars['date'] = html::span('text-nowrap', $date);
|
||||
|
||||
if (empty($user)) {
|
||||
$label = 'shortheaderdate';
|
||||
}
|
||||
|
||||
$out = html::span(null, $rcmail->gettext(['name' => $label, 'vars' => $vars]));
|
||||
|
||||
return html::div($attrib, $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Priority header value into a localized string
|
||||
*/
|
||||
public static function localized_priority($value)
|
||||
{
|
||||
$labels_map = [
|
||||
'1' => 'highest',
|
||||
'2' => 'high',
|
||||
'3' => 'normal',
|
||||
'4' => 'low',
|
||||
'5' => 'lowest',
|
||||
];
|
||||
|
||||
if ($value && !empty($labels_map[$value])) {
|
||||
return rcmail::get_instance()->gettext($labels_map[$value]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block to show full message headers
|
||||
*/
|
||||
public static function message_full_headers($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$html = html::div(['id' => "all-headers", 'class' => "all", 'style' => 'display:none'],
|
||||
html::div(['id' => 'headers-source'], ''));
|
||||
|
||||
$html .= html::div([
|
||||
'class' => "more-headers show-headers",
|
||||
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('show-headers','',this)",
|
||||
'title' => $rcmail->gettext('togglefullheaders')
|
||||
], '');
|
||||
|
||||
$rcmail->output->add_gui_object('all_headers_row', 'all-headers');
|
||||
$rcmail->output->add_gui_object('all_headers_box', 'headers-source');
|
||||
|
||||
return html::div($attrib, $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'messagebody' GUI object
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message body
|
||||
*/
|
||||
public static function message_body($attrib)
|
||||
{
|
||||
if (
|
||||
empty(self::$MESSAGE)
|
||||
|| (!is_array(self::$MESSAGE->parts) && empty(self::$MESSAGE->body))
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmailMsgBody';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$safe_mode = self::$MESSAGE->is_safe || !empty($_GET['_safe']);
|
||||
$out = '';
|
||||
$part_no = 0;
|
||||
|
||||
$header_attrib = [];
|
||||
foreach ($attrib as $attr => $value) {
|
||||
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs)) {
|
||||
$header_attrib[$regs[1]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty(self::$MESSAGE->parts)) {
|
||||
foreach (self::$MESSAGE->parts as $part) {
|
||||
if ($part->type == 'headers') {
|
||||
$out .= html::div('message-partheaders', self::message_headers(count($header_attrib) ? $header_attrib : null, $part->headers));
|
||||
}
|
||||
else if ($part->type == 'content') {
|
||||
// unsupported (e.g. encrypted)
|
||||
if (!empty($part->realtype)) {
|
||||
if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
|
||||
if (
|
||||
!empty($_SESSION['browser_caps']['pgpmime'])
|
||||
&& ($pgp_mime_part = self::$MESSAGE->get_multipart_encrypted_part())
|
||||
) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('externalmessagedecryption'));
|
||||
$rcmail->output->set_env('pgp_mime_part', $pgp_mime_part->mime_id);
|
||||
$rcmail->output->set_env('pgp_mime_container', '#' . $attrib['id']);
|
||||
$rcmail->output->add_label('loadingdata');
|
||||
}
|
||||
|
||||
if (!self::$MESSAGE->encrypted_part) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('encryptedmessage'));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (!$part->size) {
|
||||
continue;
|
||||
}
|
||||
// Check if we have enough memory to handle the message in it
|
||||
// #1487424: we need up to 10x more memory than the body
|
||||
else if (!rcube_utils::mem_check($part->size * 10)) {
|
||||
$out .= self::part_too_big_message(self::$MESSAGE, $part->mime_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fetch part body
|
||||
$body = self::$MESSAGE->get_part_body($part->mime_id, true);
|
||||
|
||||
// message is cached but not exists (#1485443), or other error
|
||||
if ($body === false) {
|
||||
// Don't bail out if it is only one-of-many part of the message (#6854)
|
||||
if (strlen($out)) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('messageopenerror'));
|
||||
continue;
|
||||
}
|
||||
|
||||
self::message_error();
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
|
||||
['part' => $part, 'prefix' => '', 'message' => self::$MESSAGE]);
|
||||
|
||||
// Set attributes of the part container
|
||||
$container_class = $part->ctype_secondary == 'html' ? 'message-htmlpart' : 'message-part';
|
||||
$container_id = $container_class . (++$part_no);
|
||||
$container_attrib = ['class' => $container_class, 'id' => $container_id];
|
||||
|
||||
$body_args = [
|
||||
'safe' => $safe_mode,
|
||||
'plain' => !$rcmail->config->get('prefer_html'),
|
||||
'css_prefix' => 'v' . $part_no,
|
||||
'body_class' => 'rcmBody',
|
||||
'container_id' => $container_id,
|
||||
'container_attrib' => $container_attrib,
|
||||
];
|
||||
|
||||
// Parse the part content for display
|
||||
$body = self::print_body($body, $part, $body_args);
|
||||
|
||||
// check if the message body is PGP encrypted
|
||||
if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) {
|
||||
$rcmail->output->set_env('is_pgp_content', '#' . $container_id);
|
||||
}
|
||||
|
||||
if ($part->ctype_secondary == 'html') {
|
||||
$body = self::html4inline($body, $body_args);
|
||||
}
|
||||
|
||||
$out .= html::div($body_args['container_attrib'], $plugin['prefix'] . $body);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Check if we have enough memory to handle the message in it
|
||||
// #1487424: we need up to 10x more memory than the body
|
||||
if (isset(self::$MESSAGE->body) && !rcube_utils::mem_check(strlen(self::$MESSAGE->body) * 10)) {
|
||||
$out .= self::part_too_big_message(self::$MESSAGE, 0);
|
||||
}
|
||||
else {
|
||||
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
|
||||
['part' => self::$MESSAGE, 'prefix' => '']);
|
||||
|
||||
$out .= html::div('message-part',
|
||||
$plugin['prefix'] . self::plain_body(self::$MESSAGE->body));
|
||||
}
|
||||
}
|
||||
|
||||
// list images after mail body
|
||||
if ($rcmail->config->get('inline_images', true) && !empty(self::$MESSAGE->attachments)) {
|
||||
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
|
||||
$show_label = rcube::Q($rcmail->gettext('showattachment'));
|
||||
$download_label = rcube::Q($rcmail->gettext('download'));
|
||||
|
||||
foreach (self::$MESSAGE->attachments as $attach_prop) {
|
||||
// Content-Type: image/*...
|
||||
if ($mimetype = self::part_image_type($attach_prop)) {
|
||||
// Skip inline images
|
||||
if (!self::is_attachment(self::$MESSAGE, $attach_prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// display thumbnails
|
||||
if ($thumbnail_size) {
|
||||
$supported = in_array($mimetype, self::$CLIENT_MIMETYPES);
|
||||
$show_link_attr = [
|
||||
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
|
||||
'onclick' => sprintf(
|
||||
'%s.command(\'load-attachment\',\'%s\',this); return false',
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
$attach_prop->mime_id
|
||||
)
|
||||
];
|
||||
$download_link_attr = [
|
||||
'href' => $show_link_attr['href'] . '&_download=1',
|
||||
];
|
||||
$show_link = html::a($show_link_attr + ['class' => 'open'], $show_label);
|
||||
$download_link = html::a($download_link_attr + ['class' => 'download'], $download_label);
|
||||
|
||||
$out .= html::p(['class' => 'image-attachment', 'style' => $supported ? '' : 'display:none'],
|
||||
html::a($show_link_attr + ['class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)],
|
||||
html::img([
|
||||
'class' => 'image-thumbnail',
|
||||
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image') . '&_thumb=1',
|
||||
'title' => $attach_prop->filename,
|
||||
'alt' => $attach_prop->filename,
|
||||
'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
|
||||
'onload' => $supported ? '' : '$(this).parents(\'p.image-attachment\').show()',
|
||||
])
|
||||
) .
|
||||
html::span('image-filename', rcube::Q($attach_prop->filename)) .
|
||||
html::span('image-filesize', rcube::Q(self::message_part_size($attach_prop))) .
|
||||
html::span('attachment-links', ($supported ? $show_link . ' ' : '') . $download_link) .
|
||||
html::br(['style' => 'clear:both'])
|
||||
);
|
||||
}
|
||||
else {
|
||||
$out .= html::tag('fieldset', 'image-attachment',
|
||||
html::tag('legend', 'image-filename', rcube::Q($attach_prop->filename)) .
|
||||
html::p(['align' => 'center'],
|
||||
html::img([
|
||||
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image'),
|
||||
'title' => $attach_prop->filename,
|
||||
'alt' => $attach_prop->filename,
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tell client that there are blocked remote objects
|
||||
if (self::$REMOTE_OBJECTS && !$safe_mode) {
|
||||
$rcmail->output->set_env('blockedobjects', true);
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('messagebody', $attrib['id']);
|
||||
|
||||
return html::div($attrib, $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a HTML notice element for too big message parts
|
||||
*
|
||||
* @param rcube_message $message Email message object
|
||||
* @param string $part_id Message part identifier
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
public static function part_too_big_message($message, $part_id)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$token = $rcmail->get_request_token();
|
||||
$url = $rcmail->url([
|
||||
'task' => 'mail',
|
||||
'action' => 'get',
|
||||
'download' => 1,
|
||||
'uid' => $message->uid,
|
||||
'part' => $part_id,
|
||||
'mbox' => $message->folder,
|
||||
'token' => $token,
|
||||
]);
|
||||
|
||||
return html::span('part-notice', $rcmail->gettext('messagetoobig')
|
||||
. ' ' . html::a($url, $rcmail->gettext('download')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle disposition notification requests
|
||||
*
|
||||
* @param rcube_message $message Email message object
|
||||
*/
|
||||
public static function mdn_request_handler($message)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($message->headers->mdn_to
|
||||
&& $message->context === null
|
||||
&& !empty($message->sender['mailto'])
|
||||
&& empty($message->headers->flags['MDNSENT'])
|
||||
&& empty($message->headers->flags['SEEN'])
|
||||
&& ($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
|
||||
&& $message->folder != $rcmail->config->get('drafts_mbox')
|
||||
&& $message->folder != $rcmail->config->get('sent_mbox')
|
||||
) {
|
||||
$mdn_cfg = intval($rcmail->config->get('mdn_requests'));
|
||||
$exists = $mdn_cfg == 1;
|
||||
|
||||
// Check sender existence in contacts
|
||||
// 3 and 4 = my contacts, 5 and 6 = trusted senders
|
||||
if ($mdn_cfg == 3 || $mdn_cfg == 4 || $mdn_cfg == 5 || $mdn_cfg == 6) {
|
||||
$type = rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
|
||||
if ($mdn_cfg == 3 || $mdn_cfg == 4) {
|
||||
$type |= rcube_addressbook::TYPE_WRITEABLE | rcube_addressbook::TYPE_RECIPIENT;
|
||||
}
|
||||
|
||||
if ($rcmail->contact_exists($message->sender['mailto'], $type)) {
|
||||
$exists = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
// Send MDN
|
||||
if (rcmail_action_mail_sendmdn::send_mdn($message, $smtp_error)) {
|
||||
$rcmail->output->show_message('receiptsent', 'confirmation');
|
||||
}
|
||||
else if ($smtp_error && is_string($smtp_error)) {
|
||||
$rcmail->output->show_message($smtp_error, 'error');
|
||||
}
|
||||
else if ($smtp_error && !empty($smtp_error['label'])) {
|
||||
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('errorsendingreceipt', 'error');
|
||||
}
|
||||
}
|
||||
else if ($mdn_cfg != 2 && $mdn_cfg != 4 && $mdn_cfg != 6) {
|
||||
// Ask the user
|
||||
$rcmail->output->add_label('sendreceipt', 'mdnrequest', 'send', 'sendalwaysto', 'ignore');
|
||||
$rcmail->output->set_env('mdn_request_save', $mdn_cfg == 3 || $mdn_cfg == 5 ? $mdn_cfg : 0);
|
||||
$rcmail->output->set_env('mdn_request_sender', $message->sender);
|
||||
$rcmail->output->set_env('mdn_request', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the message part is a normal attachment
|
||||
*
|
||||
* @param rcube_message $message Message object
|
||||
* @param rcube_message_part $part Message part
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_attachment($message, $part)
|
||||
{
|
||||
// Inline attachment with Content-Id specified
|
||||
if (!empty($part->content_id) && $part->disposition == 'inline') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any image attached to multipart/related message (#7184)
|
||||
$parent_id = preg_replace('/\.[0-9]+$/', '', $part->mime_id);
|
||||
$parent = $message->mime_parts[$parent_id] ?? null;
|
||||
|
||||
if ($parent && $parent->mimetype == 'multipart/related') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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: |
|
||||
| Display a mail message similar as a usual mail application does |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_viewsource extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (!empty($_GET['_save'])) {
|
||||
$rcmail->request_security_check(rcube_utils::INPUT_GET);
|
||||
}
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
// similar code as in program/steps/mail/get.inc
|
||||
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET)) {
|
||||
if ($pos = strpos($uid, '.')) {
|
||||
$message = new rcube_message($uid);
|
||||
$headers = $message->headers;
|
||||
$part_id = substr($uid, $pos + 1);
|
||||
}
|
||||
else {
|
||||
$headers = $rcmail->storage->get_message_headers($uid);
|
||||
}
|
||||
|
||||
$charset = $headers->charset ?: $rcmail->config->get('default_charset', RCUBE_CHARSET);
|
||||
|
||||
if (!empty($_GET['_save'])) {
|
||||
$subject = rcube_mime::decode_header($headers->subject, $headers->charset);
|
||||
$filename = self::filename_from_subject(mb_substr($subject, 0, 128));
|
||||
$filename = ($filename ?: $uid) . '.eml';
|
||||
|
||||
$rcmail->output->download_headers($filename, [
|
||||
'length' => $headers->size,
|
||||
'type' => 'text/plain',
|
||||
'type_charset' => $charset,
|
||||
]);
|
||||
}
|
||||
else {
|
||||
// Make sure it works in an iframe (#9084)
|
||||
$rcmail->output->page_headers();
|
||||
|
||||
header("Content-Type: text/plain; charset={$charset}");
|
||||
}
|
||||
|
||||
if (isset($part_id) && isset($message)) {
|
||||
$message->get_part_body($part_id, empty($_GET['_save']), 0, -1);
|
||||
}
|
||||
else {
|
||||
$rcmail->storage->print_raw_body($uid, empty($_GET['_save']));
|
||||
}
|
||||
}
|
||||
else {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Message UID $uid not found"
|
||||
],
|
||||
true, true
|
||||
);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert message subject into filename
|
||||
*/
|
||||
public static function filename_from_subject($str)
|
||||
{
|
||||
$str = preg_replace('/[:\t\n\r\0\x0B\/]+\s*/', ' ', $str);
|
||||
|
||||
return trim($str, " \t\n\r\0\x0B./_");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user