Commit d5527ac1 authored by Victor Emanouilov's avatar Victor Emanouilov
Browse files

[ENH] EmailFolder display Tiki-stored message in cypht similar to an IMAP...

[ENH] EmailFolder display Tiki-stored message in cypht similar to an IMAP message - view headers, content, body structure, links; switch to a more robust mime message parser
parent 6ca46537
......@@ -22,79 +22,40 @@ class EmailParser extends Manipulator
$message_content = $file->getContents();
try {
$message = \Laminas\Mail\Message::fromString($message_content);
$message = \ZBateson\MailMimeParser\Message::from($message_content);
} catch (\Exception\RuntimeException $e) {
Feedback::error(tr('Failed parsing file %0 as an email.', $file->fileId) . '<br />' . $e->getMessage());
return false;
}
$headers = $message->getHeaders();
$result = [
'subject' => $message->getSubject(),
'body' => $message->getBodyText(),
'from' => '',
'sender' => '',
'recipient' => '',
'subject' => $message->getHeaderValue('Subject'),
'body' => $message->getContent(),
'from' => $this->getRawAddress($message->getHeader('From')),
'sender' => $this->getRawAddress($message->getHeader('Sender')),
'recipient' => $this->getRawAddress($message->getHeader('To')),
'date' => '',
'content_type' => '',
'plaintext' => '',
'html' => '',
'content_type' => $message->getHeaderValue('Content-Type'),
'plaintext' => $message->getTextContent(),
'html' => $message->getHtmlContent(),
'message_raw' => $message,
];
$from = $headers->get('From');
if ($from) {
$result['from'] = $from->getFieldValue();
}
$sender = $headers->get('Sender');
if ($sender) {
$result['sender'] = $sender->getFieldValue();
}
$recipient = $headers->get('To');
if ($recipient) {
$result['recipient'] = $recipient->getFieldValue();
}
$content_type = $headers->get('Content-Type');
$boundary = '';
if ($content_type) {
$result['content_type'] = $content_type->getType();
$boundary = $content_type->getParameter('boundary');
} else {
$result['content_type'] = '';
}
$date = $headers->get('Date');
$date = $message->getHeader('Date');
if ($date) {
$result['date'] = strtotime($date->getFieldValue());
$result['date'] = $date->getDateTime()->getTimestamp();
} else {
$result['date'] = '';
}
if ($headers->has('mime-version') && $boundary) {
try {
$mime = \Laminas\Mime\Message::createFromMessage($message_content, $boundary);
foreach ($mime->getParts() as $part) {
$content_type = '';
$headers = $part->getHeadersArray();
foreach ($headers as $header) {
if (strtolower($header[0]) == 'content-type') {
$content_type = $header[1];
}
}
if (stristr($content_type, 'text/plain')) {
$result['plaintext'] = $part->getRawContent();
}
if (stristr($content_type, 'text/html')) {
$result['html'] = $part->getRawContent();
}
}
} catch (\Exception\RuntimeException $e) {
Feedback::error(tr('Failed extracting text parts from file %0 as an email.', $file->fileId) . '<br />' . $e->getMessage());
}
}
return $result;
}
protected function getRawAddress($header) {
if ($header) {
return $header->getRawValue();
} else {
return '';
}
}
}
<?php
/**
* Tiki modules
* @package modules
* @subpackage tiki
*/
if (!defined('DEBUG_MODE')) { die(); }
/**
* Convert MimePart message parts to IMAP-compatible BODYSTRUCTURE
* @subpackage tiki/functions
* @param ZBateson\MailMimeParser\Message\Part\MimePart $part the mime message part
* @param int $part_num the mime message part number
* @return array
*/
if (!hm_exists('tiki_mime_part_to_bodystructure')) {
function tiki_mime_part_to_bodystructure($part, $part_num = '0') {
$content_type = explode('/', $part->getContentType());
$header = $part->getHeader('Content-Type');
$attributes = [];
foreach (['boundary', 'charset', 'name'] as $param) {
if ($header->hasParameter($name)) {
$attributes[$name] = $header->getValueFor($name);
}
}
$header = $part->getHeader('Content-Disposition');
$file_attributes = [];
if ($header) {
$file_attributes[$header->getValue()] = [];
if ($header->getValueFor('filename')) {
$file_attributes[$header->getValue()][] = 'filename';
$file_attributes[$header->getValue()][] = $header->getValueFor('filename');
}
}
$result = [$part_num => [
'type' => $content_type[0],
'subtype' => $content_type[1],
'attributes' => $attributes,
"id" => $part->getContentId(),
'description' => false,
'encoding' => $part->getContentTransferEncoding(),
'size' => strlen($part->getContent()),
'lines' => $part->isTextPart() ? substr_count($part->getContent(), "\n") : false,
'md5' => false,
'disposition' => $part->getContentDisposition(false),
'file_attributes' => $file_attributes,
'language' => false,
'location' => false,
]];
if ($part->getChildCount() > 0) {
$result[$part_num]['subs'] = [];
foreach ($part->getChildParts() as $i => $subpart) {
$subpart_num = $part_num.'.'.($i+1);
$result[$part_num]['subs'] = array_merge($result[$part_num]['subs'], tiki_mime_part_to_bodystructure($subpart, $subpart_num));
}
}
return $result;
}}
......@@ -13,6 +13,7 @@
if (!defined('DEBUG_MODE')) { die(); }
require_once APP_PATH.'modules/tiki/functions.php';
require_once APP_PATH.'modules/tiki/general_modules.php';
require_once APP_PATH.'modules/tiki/groupmail_modules.php';
require_once APP_PATH.'modules/tiki/calendar_modules.php';
......
......@@ -118,6 +118,12 @@ add_handler('ajax_move_to_tracker', 'move_to_tracker', true, 'tiki', 'imap_oauth
add_handler('ajax_move_to_tracker', 'save_imap_cache', true, 'imap', 'move_to_tracker', 'after');
add_handler('ajax_move_to_tracker', 'close_session_early', true, 'core', 'save_imap_cache', 'after');
add_output('ajax_move_to_tracker', 'pass_redirect_url', true, 'tiki');
setup_base_ajax_page('ajax_tiki_message_content', 'core');
add_handler('ajax_tiki_message_content', 'tiki_message_content', true);
add_handler('ajax_tiki_message_content', 'close_session_early', true, 'core');
add_output('ajax_tiki_message_content', 'filter_message_headers', true, 'imap');
add_output('ajax_tiki_message_content', 'filter_message_body', true, 'imap');
add_output('ajax_tiki_message_content', 'filter_message_struct', true, 'imap');
return array(
'allowed_pages' => array(
......@@ -129,7 +135,8 @@ return array(
'ajax_add_to_calendar',
'ajax_update_participant_status',
'ajax_remove_from_calendar',
'ajax_move_to_tracker'
'ajax_move_to_tracker',
'ajax_tiki_message_content'
),
'allowed_get' => array(
),
......@@ -141,8 +148,11 @@ return array(
'allowed_post' => array(
'imap_server_id' => FILTER_VALIDATE_INT,
'imap_msg_uid' => FILTER_SANITIZE_STRING,
'imap_msg_part' => FILTER_SANITIZE_STRING,
'folder' => FILTER_SANITIZE_STRING,
'msgid' => FILTER_SANITIZE_STRING,
'imap_allow_images' => FILTER_VALIDATE_BOOLEAN,
'list_path' => FILTER_SANITIZE_STRING,
'rsvp_action' => FILTER_SANITIZE_STRING,
'calendar_id' => FILTER_VALIDATE_INT,
'debug_mode' => FILTER_VALIDATE_INT,
......
......@@ -293,6 +293,38 @@ var tiki_setup_move_to_trackers = function() {
});
}
var tiki_get_message_content = function(msg_part, uid, images) {
if (!images) {
images = 0;
}
if (!uid) {
uid = $('.msg_uid').val();
}
if (uid) {
if (hm_page_name() == 'message') {
window.scrollTo(0,0);
}
Hm_Ajax.request(
[{'name': 'hm_ajax_hook', 'value': 'ajax_tiki_message_content'},
{'name': 'imap_msg_uid', 'value': uid},
{'name': 'imap_msg_part', 'value': msg_part},
{'name': 'imap_allow_images', 'value': images},
{'name': 'list_path', 'value': hm_list_path()}],
function(res) {
$('.msg_text').html('');
$('.msg_text').append(res.msg_headers);
$('.msg_text').append(res.msg_text);
$('.msg_text').append(res.msg_parts);
document.title = $('.header_subject th').text();
imap_message_view_finished();
},
[],
false
);
}
return false;
};
/* executes on onload, has access to other module code */
$(function() {
if (hm_page_name() == 'groupmail') {
......@@ -307,6 +339,10 @@ $(function() {
tiki_setup_move_to_trackers();
}
if (hm_page_name() === 'message' && hm_list_path().substr(0, 14) === 'tracker_folder') {
tiki_get_message_content();
}
if (hm_page_name() == 'settings') {
tiki_enable_oauth2_over_imap();
}
......
......@@ -134,6 +134,124 @@ class Hm_Handler_move_to_tracker extends Hm_Handler_Module {
}
}
/**
* Get message content from Tiki tracker EmailField storage and prepare for imap module display
* @subpackage tiki/handler
*/
class Hm_Handler_tiki_message_content extends Hm_Handler_Module {
public function process() {
list($success, $form) = $this->process_form(array('imap_msg_uid'));
if (! $success) {
return;
}
$this->out('msg_text_uid', $form['imap_msg_uid']);
$this->out('msg_list_path', $this->request->post['list_path']);
$part = false;
if (isset($this->request->post['imap_msg_part']) && preg_match("/[0-9\.]+/", $this->request->post['imap_msg_part'])) {
$part = $this->request->post['imap_msg_part'];
}
if (array_key_exists('imap_allow_images', $this->request->post) && $this->request->post['imap_allow_images']) {
$this->out('imap_allow_images', true);
}
$this->out('header_allow_images', $this->config->get('allow_external_image_sources'));
$trk = TikiLib::lib('trk');
$path = str_replace('tracker_folder_', '', $this->request->post['list_path']);
list ($itemId, $fieldId) = explode('_', $path);
$field = $trk->get_field_info($fieldId);
if (! $field) {
Hm_Msgs::add('ERRTracker field not found');
return;
}
$item = $trk->get_item_info($itemId);
if (! $item) {
Hm_Msgs::add('ERRTracker item not found');
return;
}
$item[$field['fieldId']] = $trk->get_item_value(null, $item['itemId'], $field['fieldId']);
$definition = Tracker_Definition::get($field['trackerId']);
$handler = $trk->get_field_handler($field, $item);
$data = $handler->getFieldData();
if (! isset($data['emails']) || ! is_array($data['emails'])) {
Hm_Msgs::add('ERRTracker field storage is broken or you are using the wrong field type');
return;
}
$email = false;
foreach ($data['emails'] as $eml) {
if ($eml['fileId'] == $form['imap_msg_uid']) {
$email = $eml;
break;
}
}
if (! $email) {
Hm_Msgs::add('ERREmail not found in related tracker item');
return;
}
if (empty($email['message_raw'])) {
Hm_Msgs::add('ERREmail could not be parsed');
return;
}
$message = $email['message_raw'];
$msg_headers = [];
foreach ($message->getAllHeaders() as $header) {
$msg_headers[$header->getName()] = $header->getRawValue();
}
$msg_struct = tiki_mime_part_to_bodystructure($message);
$msg_struct_current = [];
if ($part !== false) {
// TODO
} else {
if (!$this->user_config->get('text_only_setting', false)) {
$part = $message->getHtmlPart();
if (! $part) {
$part = $message->getTextPart();
}
// TODO: $msg_text = add_attached_images($msg_text, $form['imap_msg_uid'], $msg_struct, $imap);
} else {
$part = $message->getTextPart();
}
$msg_text = $part->getContent();
$list = explode('/', $part->getContentType());
$msg_struct_current = [
'type' => $list[0],
'subtype' => $list[1],
];
}
$this->out('msg_struct', $msg_struct);
$this->out('list_headers', get_list_headers($msg_headers));
$this->out('msg_headers', $msg_headers);
$this->out('imap_msg_part', "$part");
$this->out('use_message_part_icons', $this->user_config->get('msg_part_icons_setting', false));
$this->out('simple_msg_part_view', $this->user_config->get('simple_msg_parts_setting', false));
if ($msg_struct_current) {
$this->out('msg_struct_current', $msg_struct_current);
}
$this->out('msg_text', $msg_text);
$this->out('msg_download_args', sprintf("page=message&amp;uid=%s&amp;list_path=amp;imap_download_message=1", $form['imap_msg_uid'], $this->request->post['list_path']));
$this->out('msg_show_args', sprintf("page=message&amp;uid=%s&amp;list_path=&amp;imap_show_message=1", $form['imap_msg_uid'], $this->request->post['list_path']));
clear_existing_reply_details($this->session);
if ($part == 0) {
$msg_struct_current['type'] = 'text';
$msg_struct_current['subtype'] = 'plain';
}
$this->session->set(sprintf('reply_details__%s', $this->request->post['list_path'], $form['imap_msg_uid']),
array('ts' => time(), 'msg_struct' => $msg_struct_current, 'msg_text' => $msg_text, 'msg_headers' => $msg_headers));
}
}
/**
* Add Move to trackers button
* @subpackage tiki/output
......
......@@ -389,13 +389,16 @@ div.unseen, .unseen .subject { font-weight: 700; }
.mobile .part_desc, .mobile .part_size { margin-left: 25px; }
.mobile .unflag_send_div { float: right; clear: right; margin-top: 10px; }
#archive_val { padding-left: 20px; }
.compose_page { width: 100%; height: 100%; min-height: 400px; background-color: #fff; width: 100%; }
.attached_image { margin-right: 20px; margin-bottom: 20px; height: 200px; }
.attached_image_box { display: flex; flex-wrap: wrap; border-top: solid 1px #ddd; padding-top: 20px; padding-left: 20px; width: 100%; padding-bottom: 40px; }.compose_page { width: 100%; height: 100%; min-height: 400px; background-color: #fff; width: 100%; }
.compose_form { position: relative; display: block; padding: 20px; width: 80%; padding-left: 30px; padding-bottom: 0px; }
.compose_bcc, .compose_cc, .compose_subject, .compose_to { padding: 5px; margin: 5px; width: 100%; }
/*.compose_to { width: calc(100% - 25px); }*/
.toggle_recipients { background-color: #fff; position: absolute; padding-left: 5px; right: 9px; top: 28px; font-size: 120%; }
.compose_body { width: 100%; min-height: 300px; padding: 5px; margin: 5px; }
.smtp_send { cursor: pointer; color: #666; font-size: 125%; padding: 10px; margin-top: 10px; }
.smtp_send_archive { cursor: pointer; color: #666; font-size: 125%; padding: 10px; margin-top: 10px; margin-left: 10px; }
.smtp_section { display: none; }
.smtp_server_setup .content_title { cursor: pointer; }
.compose_server, .compose_server option { width: 100%; padding: 5px; margin: 5px; }
......@@ -1056,6 +1059,12 @@ body {
overflow: visible;
}
.msg_controls a {
width: unset;
border: unset;
letter-spacing: unset;
}
.move_to_trackers {
text-transform: none;
z-index: 1;
......
......@@ -387,7 +387,6 @@ function Message_List() {
}
if ($('input[type=checkbox]', $('.message_table')).filter(function() {return this.checked; }).length > 0) {
this.run_callbacks(completed);
Hm_Ajax.aborted = true;
return 0;
}
if (msgs[0] === "") {
......@@ -2137,7 +2136,7 @@ var imap_delete_action = function(event) {
Hm_Ajax.request(
form.serializeArray(),
function(res) {
if (typeof res.deleted_server_id !== 'undefined') {
if (res.deleted_server_id > -1 ) {
form.parent().remove();
Hm_Utils.set_unsaved_changes(1);
Hm_Folders.reload_folders(true);
......@@ -3267,6 +3266,11 @@ var smtp_delete_draft = function(id) {
);
};
var send_archive = function() {
$('.compose_post_archive').val(1);
document.getElementsByClassName("smtp_send")[0].click();
}
var save_compose_state = function(no_files, notice) {
var no_icon = true;
if (notice) {
......@@ -3302,6 +3306,9 @@ var save_compose_state = function(no_files, notice) {
function(res) {
$('.smtp_send').prop('disabled', false);
$('.smtp_send').removeClass('disabled_input');
if (res.draft_id) {
$('.compose_draft_id').val(res.draft_id);
}
if (res.draft_subject) {
$('.draft_list .draft_'+draft_id+' a').text(res.draft_subject);
}
......@@ -3355,7 +3362,7 @@ var upload_file = function(file) {
xhr.open('POST', 'tiki-ajax_services.php?controller=cypht&action=ajax&'+window.location.search.substr(1), true);
xhr.setRequestHeader('X-Requested-With', 'xmlhttprequest');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4){
if (xhr.readyState == 4){
if (hm_encrypt_ajax_requests()) {
res = Hm_Utils.json_decode(xhr.responseText);
res = Hm_Utils.json_decode(Hm_Crypt.decrypt(res.payload));
......@@ -3385,10 +3392,10 @@ var delete_attachment = function(file, link) {
return false;
};
var replace_cursor_positon = function (txtElement) {
var replace_cursor_positon = function (txtElement) {
txtElement.val('\r\n\r\n\r\n'+txtElement.val());
txtElement.prop('selectionEnd',0);
txtElement.focus();
txtElement.focus();
}
$(function() {
......@@ -3400,9 +3407,10 @@ $(function() {
$('.smtp_reset').on("click", reset_smtp_form);
$('.delete_draft').on("click", function() { smtp_delete_draft($(this).data('id')); });
$('.smtp_save').on("click", function() { save_compose_state(false, true); });
$('.smtp_send_archive').on("click", function() { send_archive(false, true); });
$('.compose_attach_button').on("click", function() { $('.compose_attach_file').trigger('click'); });
$('.compose_attach_file').on("change", function() { upload_file(this.files[0]); $('.compose_attach_file').val(''); });
$('.compose_form').on('submit', function() { Hm_Ajax.show_loading_icon(); $('.smtp_send').addClass('disabled_input'); $('.smtp_send').on("click", function() { return false; }); });
$('.compose_form').on('submit', function() { Hm_Ajax.show_loading_icon(); $('.smtp_send').addClass('disabled_input'); $('.smtp_send_archive').addClass('disabled_input'); $('.smtp_send').on("click", function() { return false; }); });
if ($('.compose_cc').val() || $('.compose_bcc').val()) {
toggle_recip_flds();
}
......@@ -3415,7 +3423,7 @@ $(function() {
save_compose_state();
}, 100);
}
}
}
});
......@@ -5168,6 +5176,38 @@ var tiki_setup_move_to_trackers = function() {
});
}
var tiki_get_message_content = function(msg_part, uid, images) {
if (!images) {
images = 0;
}
if (!uid) {
uid = $('.msg_uid').val();
}
if (uid) {
if (hm_page_name() == 'message') {
window.scrollTo(0,0);
}
Hm_Ajax.request(
[{'name': 'hm_ajax_hook', 'value': 'ajax_tiki_message_content'},
{'name': 'imap_msg_uid', 'value': uid},
{'name': 'imap_msg_part', 'value': msg_part},
{'name': 'imap_allow_images', 'value': images},
{'name': 'list_path', 'value': hm_list_path()}],
function(res) {
$('.msg_text').html('');
$('.msg_text').append(res.msg_headers);
$('.msg_text').append(res.msg_text);
$('.msg_text').append(res.msg_parts);
document.title = $('.header_subject th').text();
imap_message_view_finished();
},
[],
false
);
}
return false;
};
/* executes on onload, has access to other module code */
$(function() {
if (hm_page_name() == 'groupmail') {
......@@ -5182,6 +5222,10 @@ $(function() {
tiki_setup_move_to_trackers();
}
if (hm_page_name() === 'message' && hm_list_path().substr(0, 14) === 'tracker_folder') {
tiki_get_message_content();
}
if (hm_page_name() == 'settings') {
tiki_enable_oauth2_over_imap();
}
......
......@@ -16,13 +16,13 @@
<tr>
<td>
{if $email.sender}
{$email.sender}
{$email.sender|escape}
{else}
{$email.from}
{$email.from|escape}
{/if}
</td>
<td>{$email.recipient}</td>
<td><a href="tiki-webmail.php?page=message&amp;uid={$email.fileId}&amp;list_path=tracker_folder_{$email.itemId}_{$email.fieldId}&amp;list_parent=tracker_{$email.trackerId}">{$email.subject}</a></td>
<td>{$email.recipient|escape}</td>
<td><a href="tiki-webmail.php?page=message&amp;uid={$email.fileId}&amp;list_path=tracker_folder_{$email.itemId}_{$email.fieldId}&amp;list_parent=tracker_{$email.trackerId}">{$email.subject|escape}</a></td>
<td>{$email.date|tiki_short_datetime}</td>
</tr>
{/foreach}
......
......@@ -143,6 +143,7 @@
"twbs/bootstrap": "^4.1",
"undojs/undojs": "1",
"vimeo/froogaloop": "1.0",
"zbateson/mail-mime-parser": "^1.3.1",
  • Don't forget to add new packages to doc/devtools/satis.json so they appear on composer.tiki.org 🤓

  • saw you added that, thanks @jonnybradley ! Just a note that we try to use version indicator with syntax "greater than or equal" (>=) in the satis.json, as restricting to version 1 will fail to include v2 later on when we move tiki to use another major version of the lib. It will simply be unavailable on composer.tiki.org.

  • Hmm, i'm not so sure that's the best thing... we should check with @jyhem who frequently points out the storage on composer.t.o isn't infinite but we keep adding new packages to it, so adding ones that we're not using might not be popular 🤓

    I think we need a script of some sort to go through all the (supported) composer.json files and weed out unused packages from composer.tiki.org - or get him lots more storage! (probably cheaper ;)

  • Yes, it is eating a huge amount of storage space but we have just too many possible version conflicts to support... each single composer update makes (sometimes) dozens of new versions for a single Tiki version. Add back several Tiki versions we support. I am not even sure your other suggestion is feasible, sorry.

Please register or sign in to reply
"zendframework/zendsearch": "dev-tiki",
"zendframework/zendservice-akismet": "~2.0",
"gidkom/php-openfire-restapi": "dev-master",
......
This diff is collapsed.
Supports Markdown
0% or .