mailjet-8.x-2.7/src/Plugin/Mail/MailjetMail.php
src/Plugin/Mail/MailjetMail.php
<?php
namespace Drupal\mailjet\Plugin\Mail;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Mail\MailInterface;
use PHPMailer\PHPMailer\PHPMailer;
/**
* Defines the default Drupal mail backend, using PHP's native mail() function.
*
* @Mail(
* id = "mailjet_mail",
* label = @Translation("Mailjet mailer"),
* description = @Translation("Sends MIME-encoded emails with embedded images
* and attachments..")
* )
*/
class MailjetMail implements MailInterface
{
protected $AllowHtml;
/**
* Concatenate and wrap the e-mail body for either
* plain-text or HTML emails.
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return array
* The formatted $message.
*/
public function format(array $message) {
$config_mailjet = \Drupal::config('mailjet.settings');
$this->AllowHtml = $config_mailjet->get('mail_headers_allow_html_mailjet');
// Join the body array into one string.
$message['body'] = implode("\n\n", $message['body']);
if (!$this->AllowHtml) {
// Convert any HTML to plain-text.
$message['body'] = MailFormatHelper::htmlToText($message['body']);
// Wrap the mail body for sending.
$message['body'] = MailFormatHelper::wrapMail($message['body']);
}
return $message;
}
/**
* Send the e-mail message.
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return bool
* TRUE if the mail was successfully accepted, otherwise FALSE.
*
* @see drupal_mail()
*/
public function mail(array $message) {
// Load the site name out of configuration.
$config = \Drupal::config('system.site');
$id = $message['id'];
$to = $message['to'];
$from = $message['from'];
$subject = $message['subject'];
$body = $message['body'];
$headers = $message['headers'];
if (isset($message['params']['subject'])) {
$subject = $message['params']['subject'];
}
$rawBody = '';
if (isset($message['params']['body'])) {
$rawBody = $message['params']['body'];
}
if (is_array($rawBody)) {
$body = reset($rawBody);
}
elseif ($rawBody instanceof MarkupInterface) {
$body = (string) $rawBody;
}
/**
* v 5.2.22
*/
if (file_exists('libraries/phpmailer/PHPMailerAutoload.php')) {
include_once 'libraries/phpmailer/PHPMailerAutoload.php';
$mailer = new \PHPMailer();
} elseif (file_exists('libraries/phpmailer/src/PHPMailer.php')) {
include_once 'libraries/phpmailer/src/PHPMailer.php';
include_once 'libraries/phpmailer/src/SMTP.php';
$mailer = new PHPMailer();
} elseif (file_exists('vendor/phpmailer/phpmailer/src/PHPMailer.php')) {
include_once 'vendor/phpmailer/phpmailer/src/PHPMailer.php';
include_once 'vendor/phpmailer/phpmailer/src/SMTP.php';
$mailer = new PHPMailer();
} elseif (file_exists('../vendor/phpmailer/phpmailer/src/PHPMailer.php')) {
include_once '../vendor/phpmailer/phpmailer/src/PHPMailer.php';
include_once '../vendor/phpmailer/phpmailer/src/SMTP.php';
$mailer = new PHPMailer();
} else {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
// If the PHPMailer class is not yet auto-loaded, try to load the library
// using Libraries API, if present.
if (function_exists('libraries_load')) {
$library = libraries_load('phpmailer');
if (empty($library) || empty($library['loaded'])) {
\Drupal::logger('mailjet')
->notice('Unable to send mail : Libraries API can not load PHPMailer library.');
\Drupal::messenger()->addMessage(
t(
'Unable to send mail: PHPMailer library does not exist.<br /><br />This module requires the PHPMailer library to be downloaded and installed separately. <br/>Get the latest PHPMailer v5 or v6 from the <a href="https://github.com/PHPMailer/PHPMailer/releases" target="_blank">official PHPMailer GitHub page</a>. <br/> Upload the "phpmailer" folder to your server inside
DRUPAL_ROOT/libraries/.'
),
'error'
);
return FALSE;
}
} else {
\Drupal::messenger()->addMessage(t('Unable to send mail: PHPMailer library does not exist.'), 'error');
\Drupal::logger('mailjet')
->notice('Unable to send mail: Libraries API and PHPMailer library does not exist.');
return FALSE;
}
}
}
$system_site_config = \Drupal::config('system.site');
$from_name = $system_site_config->get('name');
// Hack to fix reply-to issue.
$properfrom = 'test';
if (!empty($properfrom)) {
$headers['From'] = $properfrom;
}
if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) {
if (strpos($from, '<')) {
$reply = preg_replace('/>.*/', '', preg_replace('/.*</', '', $from));
}
else {
$reply = $from;
}
$headers['Reply-To'] = $reply;
}
$from = empty($from) ? $system_site_config->get('mail') : $from;
if (empty($from)) {
\Drupal::messenger()->addMessage(t('There is no submitted from address.'), 'error');
if (\Drupal::state()->get('mailjet_debug')) {
\Drupal::logger('mailjet')
->notice('There is no submitted from address.');
}
return FALSE;
}
if (preg_match('/^"?.*"?\s*<.*>$/', $from)) {
// . == Matches any single character except line break characters \r and
// \n.
// * == Repeats the previous item zero or more times.
$from_name = preg_replace('/"?([^("\t\n)]*)"?.*$/', '$1', $from);
$from = preg_replace("/(.*)\<(.*)\>/i", '$2', $from);
}
elseif (!\Drupal::service('email.validator')->isValid($from)) {
\Drupal::messenger()->addMessage(t('The submitted from address (@from) is not valid.', ['@from' => $from]), 'error');
if (\Drupal::state()->get('mailjet_debug')) {
\Drupal::logger('mailjet')
->notice('The submitted from address (@from) is not valid.', ['@from' => $from]);
}
return FALSE;
}
// Defines the From value $from_nameto what we expect.
$mailer->SetFrom($from, $from_name);
// Create the list of 'To:' recipients.
$torecipients = explode(',', $to);
foreach ($torecipients as $torecipient) {
if (strpos($torecipient, '<') !== FALSE) {
$toparts = explode(' <', $torecipient);
$toname = $toparts[0];
$toaddr = rtrim($toparts[1], '>');
}
else {
$toname = '';
$toaddr = $torecipient;
}
$mailer->AddAddress($toaddr, $toname);
}
// Parse the headers of the message and set the PHPMailer object's settings
// accordingly.
foreach ($headers as $key => $value) {
switch (mb_strtolower($key)) {
case 'from':
if ($from == NULL or $from == '') {
// If a from value was already given, then set based on header.
// Should be the most common situation since drupal_mail moves the
// from to headers.
$from = $value;
$mailer->From = $value;
// Then from can be out of sync with from_name !
$mailer->FromName = '';
$mailer->Sender = $value;
}
break;
case 'content-type':
// Parse several values on the Content-type header, storing them in
// an array like key=value -> $vars['key']='value'.
$vars = explode('; ', $value);
foreach ($vars as $i => $var) {
if ($cut = strpos($var, '=')) {
$new_var = mb_strtolower(substr($var, $cut + 1));
$new_key = substr($var, 0, $cut);
unset($vars[$i]);
$vars[$new_key] = $new_var;
}
}
// Set the charset based on the provided value, if there is one.
$mailer->CharSet = $vars['charset'] ?? 'utf-8';
$config_mailjet = \Drupal::config('mailjet.settings');
if ($config_mailjet->get('mail_headers_allow_html_mailjet') !== 0) {
$vars[0] = 'text/html';
}
switch ($vars[0]) {
case 'text/plain':
// The message includes only a plain text part.
$mailer->IsHTML(FALSE);
$content_type = 'text/plain';
break;
case 'text/html':
// The message includes only an HTML part.
$mailer->IsHTML(TRUE);
$content_type = 'text/html';
break;
case 'multipart/related':
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
// The message includes an HTML part w/inline attachments.
$mailer->ContentType = $content_type = 'multipart/related; boundary="' . $boundary . '"';
break;
case 'multipart/alternative':
// The message includes both a plain text and an HTML part.
$mailer->ContentType = $content_type = 'multipart/alternative';
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
break;
case 'multipart/mixed':
// The message includes one or more attachments.
$mailer->ContentType = $content_type = 'multipart/mixed';
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
break;
default:
// Everything else is unsuppored by PHPMailer.
\Drupal::messenger()->addMessage(t('The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.'), 'error');
if (\Drupal::state()->get('mailjet_debug')) {
\Drupal::logger('mailjet')
->notice('The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.', ['@from' => $from]);
}
// Force the Content-Type to be text/plain.
$mailer->IsHTML(FALSE);
$content_type = 'text/plain';
}
break;
case 'reply-to':
// Only add a "reply-to" if it's not the same as "return-path".
if ($value != $headers['Return-Path']) {
if (strpos($value, '<') !== FALSE) {
$reply_to_parts = explode('<', $value);
$reply_to_name = trim($reply_to_parts[0]);
$reply_to_name = trim($reply_to_name, '"');
$reply_to_addr = rtrim($reply_to_parts[1], '>');
$mailer->AddReplyTo($reply_to_addr, $reply_to_name);
}
else {
$mailer->AddReplyTo($value);
}
}
break;
case 'content-transfer-encoding':
$mailer->Encoding = $value;
break;
case 'return-path':
case 'mime-version':
case 'x-mailer':
break;
case 'errors-to':
$mailer->AddCustomHeader('Errors-To: ' . $value);
break;
case 'cc':
$ccrecipients = explode(',', $value);
foreach ($ccrecipients as $ccrecipient) {
if (strpos($ccrecipient, '<') !== FALSE) {
$ccparts = explode(' <', $ccrecipient);
$ccname = $ccparts[0];
$ccaddr = rtrim($ccparts[1], '>');
}
else {
$ccname = '';
$ccaddr = $ccrecipient;
}
$mailer->AddBCC($ccaddr, $ccname);
}
break;
case 'bcc':
$bccrecipients = explode(',', $value);
foreach ($bccrecipients as $bccrecipient) {
if (strpos($bccrecipient, '<') !== FALSE) {
$bccparts = explode(' <', $bccrecipient);
$bccname = $bccparts[0];
$bccaddr = rtrim($bccparts[1], '>');
}
else {
$bccname = '';
$bccaddr = $bccrecipient;
}
$mailer->AddBCC($bccaddr, $bccname);
}
break;
default:
// The header key is not special - add it as is.
$mailer->AddCustomHeader($key . ': ' . $value);
}
}
$mailer->AddCustomHeader('X-Mailer:Mailjet-for-Drupal8/1.1');
$mailer->Subject = $subject;
// Processes the message's body.
switch ($content_type) {
case 'multipart/alternative':
// Split the body based on the boundary ID.
$body_parts = $this->boundarySplit($body, $boundary);
foreach ($body_parts as $body_part) {
// If plain/text within the body part, add it to $mailer->AltBody.
if (strpos($body_part, 'text/plain')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->AltBody = $body_part;
}
elseif (strpos($body_part, 'text/html')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->Body = $body_part;
}
}
break;
case 'multipart/mixed':
// Split the body based on the boundary ID.
$body_parts = $this->boundarySplit($body, $boundary);
// Determine if there is an HTML part for when adding the plain
// text part.
$text_plain = FALSE;
$text_html = FALSE;
foreach ($body_parts as $body_part) {
if (strpos($body_part, 'text/plain')) {
$text_plain = TRUE;
}
if (strpos($body_part, 'text/html')) {
$text_html = TRUE;
}
}
foreach ($body_parts as $body_part) {
// If test/plain within the body part, add it to either
// $mailer->AltBody or $mailer->Body, depending on whether there is
// also a text/html part ot not.
if (strpos($body_part, 'multipart/alternative')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Get boundary ID from the Content-Type header.
$boundary2 = $this->getSubstrings($body_part, 'boundary', '"', '"');
// Split the body based on the boundary ID.
$body_parts2 = $this->boundarySplit($body_part, $boundary2);
foreach ($body_parts2 as $body_part2) {
// If plain/text within the body part, add it to $mailer->AltBody.
if (strpos($body_part2, 'text/plain')) {
// Clean up the text.
$body_part2 = trim($this->removeHeaders(trim($body_part2)));
// Include it as part of the mail object.
$mailer->AltBody = $body_part2;
$mailer->ContentType = 'multipart/mixed';
}
elseif (strpos($body_part2, 'text/html')) {
// Clean up the text.
$body_part2 = trim($this->removeHeaders(trim($body_part2)));
// Include it as part of the mail object.
$mailer->Body = $body_part2;
$mailer->ContentType = 'multipart/mixed';
}
}
}
elseif (strpos($body_part, 'text/plain')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
if ($text_html) {
$mailer->AltBody = $body_part;
$mailer->IsHTML(TRUE);
$mailer->ContentType = 'multipart/mixed';
}
else {
$mailer->Body = $body_part;
$mailer->IsHTML(FALSE);
$mailer->ContentType = 'multipart/mixed';
}
}
elseif (strpos($body_part, 'text/html')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->Body = $body_part;
$mailer->IsHTML(TRUE);
$mailer->ContentType = 'multipart/mixed';
}
elseif (strpos($body_part, 'Content-Disposition: attachment;')) {
$file_path = $this->getSubstrings($body_part, 'filename=', '"', '"');
$file_name = $this->getSubstrings($body_part, ' name=', '"', '"');
$file_encoding = $this->getSubstrings($body_part, 'Content-Transfer-Encoding', ' ', "\n");
$file_type = $this->getSubstrings($body_part, 'Content-Type', ' ', ';');
if (file_exists($file_path)) {
if (!$mailer->AddAttachment($file_path, $file_name, $file_encoding, $file_type)) {
\Drupal::messenger()->addMessage(t('Attahment could not be found or accessed.'));
}
}
else {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
if (mb_strtolower($file_encoding) === 'base64') {
$attachment = base64_decode($body_part);
}
elseif (mb_strtolower($file_encoding) === 'quoted-printable') {
$attachment = quoted_printable_decode($body_part);
}
else {
$attachment = $body_part;
}
$attachment_new_filename = tempnam(realpath(\Drupal::service('file_system')->getTempDirectory()), 'smtp');
/**
@var \Drupal\file\FileRepositoryInterface $fileRepository
*/
$fileRepository = \Drupal::service('file.repository');
$fileRepository->writeData($attachment, $attachment_new_filename, FileSystemInterface::EXISTS_REPLACE);
if (!$mailer->AddAttachment($file_path, $file_name)) {
\Drupal::messenger()->addMessage(t('Attachment could not be found or accessed.'));
}
}
}
}
break;
default:
$mailer->Body = $body;
break;
}
$config_mailjet = \Drupal::config('mailjet.settings');
// Set the authentication settings.
$username = $config_mailjet->get('mailjet_username');
$password = $config_mailjet->get('mailjet_password');
// If username and password are given, use SMTP authentication.
if ($username != '' && $password != '') {
$mailer->SMTPAuth = TRUE;
$mailer->Username = $username;
$mailer->Password = $password;
}
// Set the protocol prefix for the smtp host.
$protocol = !empty($config->get('mailjet_protocol')) ? $config->get('mailjet_protocol') : 'standard';
switch ($protocol) {
case 'ssl':
$mailer->SMTPSecure = 'ssl';
break;
case 'tls':
$mailer->SMTPSecure = 'tls';
break;
default:
$mailer->SMTPSecure = '';
}
// Set other connection settings.
$mailer->Host = !empty($config->get('mailjet_host')) ? $config->get('mailjet_host') : 'in-v3.mailjet.com';
$mailer->Port = !empty($config->get('mmailjet_port')) ? $config->get('mailjet_port') : '587';
$mailer->Mailer = 'smtp';
if (\Drupal::state()->get('mailjet_debug')) {
\Drupal::logger('mailjet')
->notice('Sending mail to: @to', ['@to' => $to]);
}
// Try to send e-mail. If it fails, set watchdog entry.
if (!$mailer->send()) {
if (\Drupal::state()->get('mailjet_debug')) {
\Drupal::logger('mailjet')
->notice(
'Error sending e-mail from @from to @to : @error_message',
[
'@from' => $from,
'@to' => $to,
'@error_message' => $mailer->ErrorInfo,
]
);
}
return FALSE;
}
$mailer->SmtpClose();
return TRUE;
}
/**
* Splits the input into parts based on the given boundary.
* Swiped from Mail::MimeDecode, with modifications based on Drupal's coding
* standards and this bug report: http://pear.php.net/bugs/bug.php?id=6495
*
* @param string $input
* A string containing the body text to parse.
* @param string $boundary
* A string with the boundary string to parse on.
*
* @return array
* An array containing the resulting mime parts.
*/
protected function boundarySplit($input, $boundary) {
$parts = [];
$bs_possible = mb_substr($boundary, 2, -2);
$bs_check = '\"' . $bs_possible . '\"';
if ($boundary == $bs_check) {
$boundary = $bs_possible;
}
$tmp = explode('--' . $boundary, $input);
for ($i = 1, $iMax = count($tmp); $i < $iMax; $i++) {
if (trim($tmp[$i])) {
$parts[] = $tmp[$i];
}
}
return $parts;
}
/**
* Strips the headers from the body part.
*
* @param string $input
* A string containing the body part to strip.
*
* @return string
* A string with the stripped body part.
*/
protected function removeHeaders(string $input): string {
$part_array = explode("\n", $input);
if (strpos($part_array[0], 'Content') !== FALSE) {
if (strpos($part_array[1], 'Content') !== FALSE) {
if (strpos($part_array[2], 'Content') !== FALSE) {
array_shift($part_array);
array_shift($part_array);
array_shift($part_array);
}
else {
array_shift($part_array);
array_shift($part_array);
}
}
else {
array_shift($part_array);
}
}
return implode("\n", $part_array);
}
/**
* Returns a string that is contained within another string.
* Returns the string from within $source that is some where after $target
* and is between $beginning_character and $ending_character.
*
* @param string $source
* A string containing the text to look through.
* @param string $target
* A string containing the text in $source to start looking from.
* @param string $beginning_character
* A string containing the character just before the sought after text.
* @param string $ending_character
* A string containing the character just after the sought after text.
*
* @return string
* A string with the text found between the $beginning_character and the
* $ending_character.
*/
protected function getSubstrings($source, $target, $beginning_character, $ending_character) {
$search_start = strpos($source, $target) + 1;
$first_character = strpos($source, $beginning_character, $search_start) + 1;
$second_character = strpos($source, $ending_character, $first_character) + 1;
$substring = mb_substr($source, $first_character, $second_character - $first_character);
$string_length = mb_strlen($substring) - 1;
if ($substring[$string_length] == $ending_character) {
$substring = mb_strlen($substring, 0, $string_length);
}
return $substring;
}
}
