From cef40340cf538dd2bf3f3ec6e2924aeef2800863 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 1 Nov 2022 12:37:01 +0200 Subject: [PATCH] Email mime message fix (#2486) * email-message-fix * fix test * email mime parts message fix --- application/Espo/Core/Mail/Sender.php | 211 ++++++++++-------- tests/unit/Espo/Core/Mail/EmailSenderTest.php | 4 +- 2 files changed, 116 insertions(+), 99 deletions(-) diff --git a/application/Espo/Core/Mail/Sender.php b/application/Espo/Core/Mail/Sender.php index 96c5f0891e..129e7040a2 100644 --- a/application/Espo/Core/Mail/Sender.php +++ b/application/Espo/Core/Mail/Sender.php @@ -29,6 +29,7 @@ namespace Espo\Core\Mail; +use Espo\Core\FileStorage\Manager as FileStorageManager; use Espo\Core\Mail\Exceptions\NoSmtp; use Espo\Core\Mail\Smtp\TransportFactory; use Espo\ORM\Collection; @@ -39,7 +40,6 @@ use Espo\Core\Utils\Config; use Espo\Core\Utils\Log; use Espo\Core\Mail\Account\SendingAccountProvider; use Espo\Core\Mail\Exceptions\SendingError; -use Espo\Repositories\Attachment as AttachmentRepository; use Espo\Entities\Attachment; use Espo\Entities\Email; use Espo\ORM\EntityManager; @@ -80,19 +80,22 @@ class Sender private Log $log; private TransportFactory $transportFactory; private SendingAccountProvider $accountProvider; + private FileStorageManager $fileStorageManager; public function __construct( Config $config, EntityManager $entityManager, Log $log, TransportFactory $transportFactory, - SendingAccountProvider $accountProvider + SendingAccountProvider $accountProvider, + FileStorageManager $fileStorageManager ) { $this->config = $config; $this->entityManager = $entityManager; $this->log = $log; $this->transportFactory = $transportFactory; $this->accountProvider = $accountProvider; + $this->fileStorageManager = $fileStorageManager; $this->useGlobal(); } @@ -440,114 +443,100 @@ class Sender $attachmentCollection[] = $attachment; } - if (count($attachmentCollection)) { - /** @var AttachmentRepository $attachmentRepository */ - $attachmentRepository = $this->entityManager->getRepository(Attachment::ENTITY_TYPE); + foreach ($attachmentCollection as $a) { + $contents = $a->has('contents') ? + $a->get('contents') : + $this->fileStorageManager->getContents($a); - foreach ($attachmentCollection as $a) { - if ($a->get('contents')) { - $contents = $a->get('contents'); - } - else { - $fileName = $attachmentRepository->getFilePath($a); + $attachment = new MimePart($contents); - if (!is_file($fileName)) { - continue; - } + $attachment->disposition = Mime::DISPOSITION_ATTACHMENT; + $attachment->encoding = Mime::ENCODING_BASE64; + $attachment->filename ='=?utf-8?B?' . base64_encode($a->getName() ?? '') . '?='; - $contents = file_get_contents($fileName); - } - - $attachment = new MimePart($contents); - - $attachment->disposition = Mime::DISPOSITION_ATTACHMENT; - $attachment->encoding = Mime::ENCODING_BASE64; - $attachment->filename ='=?utf-8?B?' . base64_encode($a->get('name')) . '?='; - - if ($a->get('type')) { - $attachment->type = $a->get('type'); - } - - $attachmentPartList[] = $attachment; + if ($a->getType()) { + $attachment->type = $a->getType(); } + + $attachmentPartList[] = $attachment; } - $attachmentInlineList = $email->getInlineAttachmentList(); + $inlineAttachmentPartList = $this->getInlineAttachmentPartList($email); - if (!empty($attachmentInlineList)) { - /** @var AttachmentRepository $attachmentRepository */ - $attachmentRepository = $this->entityManager->getRepository(Attachment::ENTITY_TYPE); - - foreach ($attachmentInlineList as $a) { - if ($a->get('contents')) { - $contents = $a->get('contents'); - } - else { - $fileName = $attachmentRepository->getFilePath($a); - - if (!is_file($fileName)) { - continue; - } - - $contents = file_get_contents($fileName); - } - - $attachment = new MimePart($contents); - - $attachment->disposition = Mime::DISPOSITION_INLINE; - $attachment->encoding = Mime::ENCODING_BASE64; - $attachment->id = $a->id; - - if ($a->getType()) { - $attachment->type = $a->getType(); - } - - $attachmentPartList[] = $attachment; - } - } - - $message->setSubject($email->get('name')); + $message->setSubject($email->getSubject() ?? ''); $body = new MimeMessage(); - $textPart = new MimePart($email->getBodyPlainForSending()); + $textPart = (new MimePart($email->getBodyPlainForSending())) + ->setType('text/plain') + ->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE) + ->setCharset('utf-8'); - $textPart->type = 'text/plain'; - $textPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; - $textPart->charset = 'utf-8'; + $htmlPart = $email->isHtml() ? + (new MimePart($email->getBodyForSending())) + ->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE) + ->setType('text/html') + ->setCharset('utf-8') : + null; - $htmlPart = null; + $messageType = null; - $isHtml = $email->isHtml(); - - if ($isHtml) { - $htmlPart = new MimePart($email->getBodyForSending()); - - $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; - $htmlPart->type = 'text/html'; - $htmlPart->charset = 'utf-8'; - } - - if (!empty($attachmentPartList)) { - $messageType = 'multipart/related'; - - if ($isHtml) { - $content = new MimeMessage(); - - $content->addPart($textPart); - $content->addPart($htmlPart); + $hasAttachments = count($attachmentPartList) !== 0; + $hasInlineAttachments = count($inlineAttachmentPartList) !== 0; + if ($hasAttachments || $hasInlineAttachments) { + if ($htmlPart) { $messageType = 'multipart/mixed'; - $contentPart = new MimePart($content->generateMessage()); + $alternative = (new MimeMessage()) + ->addPart($textPart) + ->addPart($htmlPart); - $contentPart->type = "multipart/alternative;\n boundary=\"" . - $content->getMime()->boundary() . '"'; + $alternativePart = (new MimePart($alternative->generateMessage())) + ->setType('multipart/alternative') + ->setBoundary($alternative->getMime()->boundary()); - $body->addPart($contentPart); + if ($hasInlineAttachments && $hasAttachments) { + $related = (new MimeMessage())->addPart($alternativePart); + + foreach ($inlineAttachmentPartList as $attachmentPart) { + $related->addPart($attachmentPart); + } + + $body->addPart( + (new MimePart($related->generateMessage())) + ->setType('multipart/related') + ->setBoundary($related->getMime()->boundary()) + ); + } + + if ($hasInlineAttachments && !$hasAttachments) { + $messageType = 'multipart/related'; + + $body->addPart($alternativePart); + + foreach ($inlineAttachmentPartList as $attachmentPart) { + $body->addPart($attachmentPart); + } + } + + if (!$hasInlineAttachments) { + $body->addPart( + (new MimePart($alternative->generateMessage())) + ->setType('multipart/related') + ->setBoundary($alternative->getMime()->boundary()) + ); + } } - else { + + if (!$htmlPart) { + $messageType = 'multipart/related'; + $body->addPart($textPart); + + foreach ($inlineAttachmentPartList as $attachmentPart) { + $body->addPart($attachmentPart); + } } foreach ($attachmentPartList as $attachmentPart) { @@ -555,7 +544,7 @@ class Sender } } else { - if ($isHtml) { + if ($email->isHtml()) { $body->setParts([$textPart, $htmlPart]); $messageType = 'multipart/alternative'; @@ -569,7 +558,7 @@ class Sender $message->setBody($body); - if ($messageType == 'text/plain') { + if ($messageType === 'text/plain') { if ($message->getHeaders()->has('content-type')) { $message->getHeaders()->removeHeader('content-type'); } @@ -590,7 +579,7 @@ class Sender $message->setEncoding('UTF-8'); try { - $messageId = $email->get('messageId'); + $messageId = $email->getMessageId(); if ( empty($messageId) || @@ -610,11 +599,9 @@ class Sender $messageId = substr($messageId, 1, strlen($messageId) - 2); } - $messageIdHeader = new MessageIdHeader(); - - $messageIdHeader->setId($messageId); - - $message->getHeaders()->addHeader($messageIdHeader); + $message->getHeaders()->addHeader( + (new MessageIdHeader())->setId($messageId) + ); assert($this->transport !== null); @@ -634,6 +621,34 @@ class Sender $this->useGlobal(); } + /** + * @return MimePart[] + */ + private function getInlineAttachmentPartList(Email $email): array + { + $list = []; + + foreach ($email->getInlineAttachmentList() as $a) { + $contents = $a->has('contents') ? + $a->get('contents') : + $this->fileStorageManager->getContents($a); + + $attachment = new MimePart($contents); + + $attachment->disposition = Mime::DISPOSITION_INLINE; + $attachment->encoding = Mime::ENCODING_BASE64; + $attachment->id = $a->getId(); + + if ($a->getType()) { + $attachment->type = $a->getType(); + } + + $list[] = $attachment; + } + + return $list; + } + /** * @return never * @throws SendingError diff --git a/tests/unit/Espo/Core/Mail/EmailSenderTest.php b/tests/unit/Espo/Core/Mail/EmailSenderTest.php index 1442f34ad6..e344f15f84 100644 --- a/tests/unit/Espo/Core/Mail/EmailSenderTest.php +++ b/tests/unit/Espo/Core/Mail/EmailSenderTest.php @@ -40,6 +40,7 @@ use Espo\Entities\{ }; use Espo\Core\{ + FileStorage\Manager, Mail\Account\Account, Mail\Account\SendingAccountProvider, Mail\EmailSender, @@ -74,7 +75,8 @@ class EmailSenderTest extends \PHPUnit\Framework\TestCase $entityManager, $log, $transportFactory, - $accountProvider + $accountProvider, + $this->createMock(Manager::class) ); $this->emailSender = $emailSender;