Skip to content
Snippets Groups Projects
Commit f019379f authored by Oleksandr Tymoshchuk's avatar Oleksandr Tymoshchuk Committed by Ivan Doroshenko
Browse files

Issue #3351237 by o_tymoshchuk: Add support for Inline Attachments

parent 6b0b6a5a
No related branches found
No related tags found
1 merge request!10#3351237: Add support for Inline Attachments.
......@@ -3,3 +3,4 @@ endpoint: 'https://us1.unione.io/en/transactional/api/v1/'
track_click: 1
track_read: 1
debug_mode: 0
use_inline_attachments: 0
......@@ -16,3 +16,6 @@ unione.settings:
debug_mode:
type: boolean
label: 'Debug Mode'
use_inline_attachments:
type: boolean
label: 'Use Inline Attachments'
......@@ -79,6 +79,13 @@ class UniOneAdminSettingsForm extends ConfigFormBase {
'#collapsed' => TRUE,
];
$form['advanced_settings']['use_inline_attachments'] = [
'#title' => $this->t('Use inline attachments'),
'#type' => 'checkbox',
'#default_value' => $config->get('use_inline_attachments'),
'#description' => $this->t('Enable to include images in email body instead of loading them from external URL.'),
];
$form['advanced_settings']['tracking'] = [
'#type' => 'fieldset',
'#title' => $this->t('Tracking'),
......@@ -121,7 +128,7 @@ class UniOneAdminSettingsForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('unione.settings');
$config_keys = [
'api_key', 'endpoint', 'track_click', 'track_read', 'debug_mode',
'api_key', 'endpoint', 'track_click', 'track_read', 'debug_mode', 'use_inline_attachments',
];
foreach ($config_keys as $config_key) {
......
......@@ -3,14 +3,17 @@
namespace Drupal\unione\Plugin\Mail;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Mail\MailInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Html2Text\Html2Text;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Unione\UnioneClient;
/**
* Default UniOne mail system plugin.
*
......@@ -61,6 +64,13 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
*/
protected EntityStorageInterface $fileStorage;
/**
* The MIME type guesser.
*
* @var \Symfony\Component\Mime\MimeTypeGuesserInterface
*/
protected MimeTypeGuesserInterface $mimeTypeGuesser;
/**
* The debug mode.
*
......@@ -77,13 +87,16 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
* A logger instance.
* @param \Drupal\Core\Entity\EntityStorageInterface $file_storage
* The file storage service.
* @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mime_type_guesser
* The MIME type interface.
*/
public function __construct(ImmutableConfig $settings, LoggerInterface $logger, EntityStorageInterface $file_storage) {
public function __construct(ImmutableConfig $settings, LoggerInterface $logger, EntityStorageInterface $file_storage, MimeTypeGuesserInterface $mime_type_guesser) {
$this->config = $settings;
$this->logger = $logger;
$this->fileStorage = $file_storage;
$this->client = new UnioneClient($this->config->get('api_key'), $this->config->get('endpoint'));
$this->isDebug = $this->config->get('debug_mode') ?? FALSE;
$this->mimeTypeGuesser = $mime_type_guesser;
}
/**
......@@ -93,7 +106,8 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
return new static(
$container->get('config.factory')->get('unione.settings'),
$container->get('logger.factory')->get('unione'),
$container->get('entity_type.manager')->getStorage('file')
$container->get('entity_type.manager')->getStorage('file'),
$container->get('file.mime_type.guesser.extension')
);
}
......@@ -128,14 +142,15 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
$value = "{$key}: {$value}";
});
$this->logger->error('Unable to send message from %from to: %failed_emails. %code %message', [
'%from' => $message['from'],
'%code' => $decode_response['code'] ? 'Error code: ' . $decode_response['code'] . '.' : '',
'%message' => $decode_response['message'] ? 'Error message: ' . $decode_response['message'] . '.' : '',
'%failed_emails' => $failed_emails
? implode(', ', $failed_emails) . '.'
: '',
]);
$this->logger->error('Unable to send message from %from to: %failed_emails. %code %message',
[
'%from' => $message['from'],
'%code' => $decode_response['code'] ? 'Error code: ' . $decode_response['code'] . '.' : '',
'%message' => $decode_response['message'] ? 'Error message: ' . $decode_response['message'] . '.' : '',
'%failed_emails' => $failed_emails
? implode(', ', $failed_emails) . '.'
: '',
]);
}
return $response['status'] === 'success';
......@@ -173,11 +188,20 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
'from_name' => $name,
'recipients' => $recipients,
'subject' => $message['subject'],
'body' => ['html' => $message['body'] instanceof MarkupInterface
? $message['body']->__toString()
: $message['body']],
'body' => [
'html' => $message['body'] instanceof MarkupInterface
? $message['body']->__toString()
: $message['body'],
],
'inline_attachments' => [],
];
// Set inline attachments.
if (isset($message['inline_attachments'])) {
$unione_message['inline_attachments'] = array_merge($unione_message['inline_attachments'],
$message['inline_attachments']);
}
// Remove HTML version if the message does not support HTML.
if (isset($message['params']['html']) && !$message['params']['html']) {
unset($unione_message['body']['html']);
......@@ -253,6 +277,10 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
}
}
if (!empty($this->config->get('use_inline_attachments')) && !empty($unione_message['body']['html'])) {
$this->setInlineAttachments($unione_message);
}
return [$unione_message, $message['headers']];
}
......@@ -287,4 +315,71 @@ class UniOneMail implements MailInterface, ContainerFactoryPluginInterface {
return [trim($matches['addrSpec']), trim($matches['displayName'])];
}
/**
* Creates inline attachment for given file path.
*
* @param string $path
* Absolute path to the image.
* @param string $image_id
* The attachment id.
*
* @return array|null
* Array with inline attachment info, or NULL if image was not found.
*/
public function createInlineAttachment(string $path, string $image_id): ?array {
if (UrlHelper::isValid($path)) {
// Try loading file content from the provided path.
@$content = file_get_contents($path);
$mime_type = $this->mimeTypeGuesser->guessMimeType($path);
if (!empty($mime_type) && !empty($content)) {
return [
'type' => $mime_type,
'name' => $image_id,
'content' => base64_encode($content),
];
}
}
return NULL;
}
/**
* Find and replace images with inline attachments.
*
* @param array $message
* Body string.
*/
public function setInlineAttachments(array &$message): void {
$html_dom = new \DOMDocument();
$html_dom->loadHTML($message['body']['html']);
// Extract all img elements / tags from the HTML.
$image_tags = $html_dom->getElementsByTagName('img');
if (count($image_tags) < 1) {
return;
}
$id = 0;
$inline_attachments = [];
// Loop through the image tags that DOMDocument found.
foreach ($image_tags as $image_tag) {
// Generate attachment id, that will be used in "src" image attribute.
$image_id = 'UNIONE-DRUPAL-IMAGECID' . $id++;
// Get the src attribute of the image.
$img_src = $image_tag->getAttribute('src');
if ($attachment = $this->createInlineAttachment($img_src, $image_id)) {
$inline_attachments[] = $attachment;
$image_tag->setAttribute('src', "cid:$image_id");
}
}
if (!empty($inline_attachments)) {
$body_text = $html_dom->saveHTML();
$message['body']['html'] = $body_text;
$message['inline_attachments'] = array_merge($message['inline_attachments'],
$inline_attachments);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment