chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
namespace Mautic\EmailBundle\Helper;
use Doctrine\ORM\ORMException;
use Mautic\AssetBundle\Entity\Asset;
use Mautic\CoreBundle\Factory\MauticFactory;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\EmailBundle\EmailEvents;
use Mautic\EmailBundle\Entity\Copy;
use Mautic\EmailBundle\Entity\Email;
use Mautic\EmailBundle\Entity\Stat;
use Mautic\EmailBundle\Event\EmailSendEvent;
use Mautic\EmailBundle\Exception\InvalidEmailException;
use Mautic\EmailBundle\Form\Type\ConfigType;
use Mautic\EmailBundle\Helper\DTO\AddressDTO;
use Mautic\EmailBundle\Helper\Exception\OwnerNotFoundException;
use Mautic\EmailBundle\Mailer\Exception\BatchQueueMaxException;
use Mautic\EmailBundle\Mailer\Message\MauticMessage;
use Mautic\EmailBundle\Mailer\Transport\TokenTransportInterface;
use Mautic\EmailBundle\MonitoredEmail\Mailbox;
use Mautic\LeadBundle\Entity\Lead;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Exception\RfcComplianceException;
use Symfony\Component\Mime\Header\HeaderInterface;
use Symfony\Component\Mime\Header\UnstructuredHeader;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Twig\Environment;
class MailHelper
{
public const QUEUE_RESET_TO = 'RESET_TO';
public const QUEUE_FULL_RESET = 'FULL_RESET';
public const QUEUE_DO_NOTHING = 'DO_NOTHING';
public const QUEUE_NOTHING_IF_FAILED = 'IF_FAILED';
public const QUEUE_RETURN_ERRORS = 'RETURN_ERRORS';
public const EMAIL_TYPE_TRANSACTIONAL = 'transactional';
public const EMAIL_TYPE_MARKETING = 'marketing';
/**
* @var TransportInterface
*/
protected $transport;
/**
* @var Environment
*/
protected $twig;
protected ?EventDispatcherInterface $dispatcher = null;
/**
* @var bool|MauticMessage
*/
public $message;
protected ?AddressDTO $from = null;
protected ?AddressDTO $systemFrom = null;
protected ?string $replyTo = null;
protected ?string $systemReplyTo = null;
/**
* @var string
*/
protected $returnPath;
/**
* @var array
*/
protected $errors = [];
/**
* @var array|Lead
*/
protected $lead;
/**
* @var bool
*/
protected $internalSend = false;
/**
* @var null
*/
protected $idHash;
/**
* @var bool
*/
protected $idHashState = true;
/**
* @var bool
*/
protected $appendTrackingPixel = false;
/**
* @var array
*/
protected $source = [];
/**
* @var Email|null
*/
protected $email;
protected ?string $emailType = null;
/**
* @var array
*/
protected $globalTokens = [];
/**
* @var array
*/
protected $eventTokens = [];
/**
* Tells the helper that the transport supports tokenized emails (likely HTTP API).
*
* @var bool
*/
protected $tokenizationEnabled = false;
/**
* Use queue mode when sending email through this mailer; this requires a transport that supports tokenization and the use of queue/flushQueue.
*
* @var bool
*/
protected $queueEnabled = false;
/**
* @var array
*/
protected $queuedRecipients = [];
/**
* @var array
*/
public $metadata = [];
/**
* @var string
*/
protected $subject = '';
/**
* @var string
*/
protected $plainText = '';
/**
* @var bool
*/
protected $plainTextSet = false;
/**
* @var array
*/
protected $assets = [];
/**
* @var array
*/
protected $attachedAssets = [];
/**
* @var array
*/
protected $assetStats = [];
/**
* @var array
*/
protected $headers = [];
/**
* @var array
*/
protected $body = [
'content' => '',
'contentType' => 'text/html',
'charset' => null,
];
/**
* Cache for lead owners.
*
* @var array
*/
protected static $leadOwners = [];
/**
* @var bool
*/
protected $fatal = false;
protected bool $skip = false;
/**
* Simply a md5 of the content so that event listeners can easily determine if the content has been changed.
*/
private ?string $contentHash = null;
private array $copies = [];
private array $embedImagesReplaces = [];
public function __construct(
private MauticFactory $factory,
private MailerInterface $mailer,
private FromEmailHelper $fromEmailHelper,
private CoreParametersHelper $coreParametersHelper,
private Mailbox $mailbox,
private LoggerInterface $logger,
private MailHashHelper $mailHashHelper,
private RouterInterface $router
) {
$this->transport = $this->getTransport();
$this->returnPath = $coreParametersHelper->get('mailer_return_path');
$systemFromEmail = (string) $coreParametersHelper->get('mailer_from_email');
$systemReplyToEmail = $coreParametersHelper->get('mailer_reply_to_email');
$systemFromName = $this->cleanName(
$coreParametersHelper->get('mailer_from_name')
);
$this->setDefaultFrom(false, new AddressDTO($systemFromEmail, $systemFromName));
$this->setDefaultReplyTo($systemReplyToEmail, $this->from);
// Check if batching is supported by the transport
if ($this->transport instanceof TokenTransportInterface) {
$this->tokenizationEnabled = true;
}
$this->message = $this->getMessageInstance();
}
/**
* Mirrors previous MauticFactory functionality.
*
* @param bool $cleanSlate
*
* @return $this
*/
public function getMailer($cleanSlate = true)
{
$this->reset($cleanSlate);
return $this;
}
/**
* Mirrors previous MauticFactory functionality.
*
* @param bool $cleanSlate
*
* @return $this
*/
public function getSampleMailer($cleanSlate = true)
{
return $this->getMailer($cleanSlate);
}
/**
* Send the message.
*
* @param bool $dispatchSendEvent
* @param bool $isQueueFlush (a tokenized/batch send via API such as Mandrill)
*
* @return bool
*/
public function send($dispatchSendEvent = false, $isQueueFlush = false)
{
if ($this->tokenizationEnabled && !empty($this->queuedRecipients) && !$isQueueFlush) {
// This transport uses tokenization and queue()/flushQueue() was not used therefore use them in order
// properly populate metadata for this transport
if ($result = $this->queue($dispatchSendEvent)) {
$result = $this->flushQueue(['To', 'Cc', 'Bcc']);
}
return $result;
}
// Set from email
if (!$isQueueFlush) {
$this->setFromForSingleMessage();
$this->setReplyToForSingleMessage($this->email);
} // from is set in flushQueue
if (empty($this->message->getReplyTo()) && !empty($this->getReplyTo())) {
$this->setMessageReplyTo($this->getReplyTo());
}
// Set system return path if applicable
if (!$isQueueFlush && ($bounceEmail = $this->generateBounceEmail())) {
$this->message->returnPath($bounceEmail);
} elseif (!empty($this->returnPath)) {
$this->message->returnPath($this->returnPath);
}
$this->dispatchPreSendEvent();
if (empty($this->fatal)) {
if (!$isQueueFlush) {
// Search/replace tokens if this is not a queue flush
// Generate tokens from listeners
if ($dispatchSendEvent) {
$this->dispatchSendEvent();
}
// Queue an asset stat if applicable
$this->queueAssetDownloadEntry();
}
$this->message->subject($this->subject);
// Only set body if not empty or if plain text is empty - this ensures an empty HTML body does not show for
// messages only with plain text
if (!empty($this->body['content']) || empty($this->plainText)) {
$this->message->html($this->body['content'], $this->body['charset'] ?? 'utf-8');
}
$this->setMessagePlainText();
$this->setMessageHeaders();
if (!$isQueueFlush) {
// Replace token content
$tokens = $this->getTokens();
if ($ownerSignature = $this->fromEmailHelper->getSignature()) {
$tokens['{signature}'] = $ownerSignature;
}
// Set metadata if applicable
foreach ($this->queuedRecipients as $email => $name) {
$this->message->addMetadata($email, $this->buildMetadata($name, $tokens));
}
// Replace tokens
$search = array_keys($tokens);
$replace = $tokens;
self::searchReplaceTokens($search, $replace, $this->message);
}
if (true === $this->coreParametersHelper->get('mailer_convert_embed_images')) {
$this->convertEmbedImages();
}
// Attach assets
/** @var Asset $asset */
foreach ($this->assets as $asset) {
if (!in_array($asset->getId(), $this->attachedAssets)) {
$this->attachedAssets[] = $asset->getId();
$this->attachFile(
$asset->getFilePath(),
$asset->getOriginalFileName(),
$asset->getMime()
);
}
}
try {
if (!$this->skip) {
$this->mailer->send($this->message);
}
$this->skip = false;
} catch (TransportExceptionInterface $exception) {
/*
The nature of symfony/mailer is working with transactional emails only
if a message fails to send, all the contacts on that message will be considered failed
*/
$failures = $this->tokenizationEnabled ? array_keys($this->message->getMetadata()) : [];
// Exception encountered when sending so all recipients are considered failures
$this->errors['failures'] = array_unique(
array_merge(
$failures,
array_keys((array) $this->message->getTo()),
array_keys((array) $this->message->getCc()),
array_keys((array) $this->message->getBcc())
)
);
$this->logError($exception->getMessage());
}
}
$error = empty($this->errors);
if (!$isQueueFlush) {
$this->createAssetDownloadEntries();
} // else handled in flushQueue
return $error;
}
/**
* If batching is supported and enabled, the message will be queued and will on be sent upon flushQueue().
* Otherwise, the message will be sent to the transport immediately.
*
* @param bool $dispatchSendEvent
* @param string $returnMode What should happen post send/queue to $this->message after the email send is attempted.
* Options are:
* RESET_TO resets the to recipients and resets errors
* FULL_RESET creates a new MauticMessage instance and resets errors
* DO_NOTHING leaves the current errors array and MauticMessage instance intact
* NOTHING_IF_FAILED leaves the current errors array MauticMessage instance intact if it fails, otherwise reset_to
* RETURN_ERROR return an array of [success, $errors]; only one applicable if message is queued
*
* @return bool|array
*/
public function queue($dispatchSendEvent = false, $returnMode = self::QUEUE_RESET_TO)
{
if ($this->tokenizationEnabled) {
// Dispatch event to get custom tokens from listeners
if ($dispatchSendEvent) {
$this->dispatchSendEvent();
}
// Metadata has to be set for each recipient
foreach ($this->queuedRecipients as $email => $name) {
$from = $this->fromEmailHelper->getFromAddressConsideringOwner($this->getFrom(), $this->lead, $this->email);
$fromAddress = $from->getEmail();
$tokens = $this->getTokens();
$tokens['{signature}'] = $this->fromEmailHelper->getSignature();
if (!isset($this->metadata[$fromAddress])) {
$this->metadata[$fromAddress] = [
'from' => $from,
'contacts' => [],
];
}
$this->metadata[$fromAddress]['contacts'][$email] = $this->buildMetadata($name, $tokens);
}
// Reset recipients
$this->queuedRecipients = [];
// Assume success
return (self::QUEUE_RETURN_ERRORS) ? [true, []] : true;
} else {
$success = $this->send($dispatchSendEvent);
// Reset the message for the next
$this->queuedRecipients = [];
// Reset message
switch (strtoupper($returnMode)) {
case self::QUEUE_RESET_TO:
$this->message->to();
$this->clearErrors();
break;
case self::QUEUE_NOTHING_IF_FAILED:
if ($success) {
$this->message->to();
$this->clearErrors();
}
break;
case self::QUEUE_FULL_RESET:
$this->message = $this->getMessageInstance();
$this->attachedAssets = [];
$this->clearErrors();
break;
case self::QUEUE_RETURN_ERRORS:
$this->message->to();
$errors = $this->getErrors();
$this->clearErrors();
return [$success, $errors];
case self::QUEUE_DO_NOTHING:
default:
// Nada
break;
}
return $success;
}
}
/**
* Send batched mail to mailer.
*
* @param array $resetEmailTypes Array of email types to clear after flusing the queue
*
* @return bool
*/
public function flushQueue($resetEmailTypes = ['To', 'Cc', 'Bcc'])
{
// Assume true unless there was a fatal error configuring the mailer because if tokenizationEnabled is false, the send happened in queue()
$flushed = empty($this->fatal);
if ($this->tokenizationEnabled && count($this->metadata) && $flushed) {
$errors = $this->errors;
$errors['failures'] = [];
$flushed = false;
foreach ($this->metadata as $metadatum) {
// Whatever is in the message "to" should be ignored as we will send to the contacts grouped by from addresses
// This prevents mailers such as sparkpost from sending duplicates to contacts
$this->message->to();
$this->errors = [];
$email = $this->getEmail();
if ($email && $email->getUseOwnerAsMailer()) {
$this->setFrom($metadatum['from']->getEmail(), $metadatum['from']->getName());
$this->setMessageFrom(new AddressDTO($metadatum['from']->getEmail(), $metadatum['from']->getName()));
} else {
$this->setMessageFrom($this->getFrom());
}
foreach ($metadatum['contacts'] as $email => $contact) {
$this->message->addMetadata($email, $contact);
// Add asset stats if applicable
if (!empty($contact['leadId'])) {
$this->queueAssetDownloadEntry($email, $contact);
}
$this->message->to(new Address($email, $contact['name'] ?? ''));
}
$flushed = $this->send(false, true);
// Merge errors
if (isset($this->errors['failures'])) {
$errors['failures'] = array_merge($errors['failures'], $this->errors['failures']);
unset($this->errors['failures']);
}
if (!empty($this->errors)) {
$errors = array_merge($errors, $this->errors);
}
// Clear metadata for the previous recipients
$this->message->clearMetadata();
}
$this->errors = $errors;
// Clear queued to recipients
$this->queuedRecipients = [];
$this->metadata = [];
}
foreach ($resetEmailTypes as $type) {
$this->message->{$type}();
}
return $flushed;
}
/**
* Resets the mailer.
*
* @param bool $cleanSlate
*/
public function reset($cleanSlate = true): void
{
$this->eventTokens = [];
$this->queuedRecipients = [];
$this->errors = [];
$this->lead = null;
$this->idHash = null;
$this->contentHash = null;
$this->internalSend = false;
$this->fatal = false;
$this->idHashState = true;
if ($cleanSlate) {
$this->appendTrackingPixel = false;
$this->queueEnabled = false;
$this->from = $this->getSystemFrom();
$this->replyTo = $this->getSystemReplyTo();
$this->headers = [];
$this->source = [];
$this->assets = [];
$this->globalTokens = [];
$this->attachedAssets = [];
$this->email = null;
$this->copies = [];
$this->message = $this->getMessageInstance();
$this->subject = '';
$this->plainText = '';
$this->plainTextSet = false;
$this->body = [
'content' => '',
'contentType' => 'text/html',
'charset' => null,
];
}
}
/**
* Search and replace tokens
* Adapted from \Swift_Plugins_DecoratorPlugin.
*
* @param array $search
* @param array $replace
*/
public static function searchReplaceTokens($search, $replace, MauticMessage &$message): void
{
// Body
$body = $message->getHtmlBody();
$bodyReplaced = str_ireplace($search, $replace, (string) $body, $updated);
if ($updated) {
$message->html($bodyReplaced);
}
unset($body, $bodyReplaced);
// Subject
$subject = $message->getSubject();
$bodyReplaced = str_ireplace($search, $replace, $subject, $updated);
if ($updated) {
$message->subject($bodyReplaced);
}
unset($subject, $bodyReplaced);
// Headers
/** @var HeaderInterface $header */
foreach ($message->getHeaders()->all() as $header) {
// It only makes sense to tokenize headers that can be interpreted as text.
if (!$header instanceof UnstructuredHeader) {
continue;
}
$headerBody = $header->getBody();
$bodyReplaced = str_ireplace($search, $replace, $headerBody);
$header->setBody($bodyReplaced);
}
// Parts (plaintext)
$textBody = $message->getTextBody() ?? '';
$bodyReplaced = str_ireplace($search, $replace, $textBody);
if ($textBody != $bodyReplaced) {
$textBody = strip_tags($bodyReplaced);
$message->text($textBody);
}
}
public static function getBlankPixel(): string
{
return 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
}
/**
* Add an attachment to email.
*
* @param string $filePath
* @param string $fileName
* @param string $contentType
* @param bool $inline
*/
public function attachFile($filePath, $fileName = null, $contentType = null, $inline = false): void
{
if (true === $inline) {
$this->message->embedFromPath($filePath, $fileName, $contentType);
return;
}
$this->message->attachFromPath($filePath, $fileName, $contentType);
}
/**
* @param int|Asset $asset
*/
public function attachAsset($asset): void
{
$model = $this->factory->getModel('asset');
if (!$asset instanceof Asset) {
$asset = $model->getEntity($asset);
if (null == $asset) {
return;
}
}
if ($asset->isPublished()) {
$asset->setUploadDir($this->factory->getParameter('upload_dir'));
$this->assets[$asset->getId()] = $asset;
}
}
/**
* Use a template as the body.
*
* @param string $template
* @param array $vars
* @param bool $returnContent
*
* @return void|string
*/
public function setTemplate($template, $vars = [], $returnContent = false, $charset = null)
{
if (null == $this->twig) {
$this->twig = $this->factory->getTwig();
}
$content = $this->twig->render($template, $vars);
unset($vars);
if ($returnContent) {
return $content;
}
$this->setBody($content, 'text/html', $charset);
unset($content);
}
public function setSubject($subject): void
{
$this->subject = $subject;
}
/**
* @return string
*/
public function getSubject()
{
return $this->subject;
}
/**
* Set a plain text part.
*/
public function setPlainText($content): void
{
$this->plainText = $content;
// Update the identifier for the content
$this->contentHash = md5($this->body['content'].$this->plainText);
}
/**
* @return string
*/
public function getPlainText()
{
return $this->plainText;
}
/**
* Set plain text for $this->message, replacing if necessary.
*/
protected function setMessagePlainText()
{
if ($this->tokenizationEnabled && $this->plainTextSet) {
// No need to find and replace since tokenization happens at the transport level
return;
}
$this->message->text($this->plainText);
$this->plainTextSet = true;
}
/**
* @param string $contentType
* @param bool $ignoreTrackingPixel
*/
public function setBody($content, $contentType = 'text/html', $charset = null, $ignoreTrackingPixel = false): void
{
if (!$ignoreTrackingPixel && $this->coreParametersHelper->get('mailer_append_tracking_pixel')) {
// Append tracking pixel
$trackingImg = '<img height="1" width="1" src="{tracking_pixel}" alt="" />';
if (str_contains((string) $content, '</body>')) {
$content = str_replace('</body>', $trackingImg.'</body>', $content);
} else {
$content .= $trackingImg;
}
}
// Update the identifier for the content
$this->contentHash = md5($content.$this->plainText);
$this->body = [
'content' => $content,
'contentType' => $contentType,
'charset' => $charset,
];
}
private function convertEmbedImages(): void
{
$content = $this->message->getHtmlBody();
$matches = [];
$content = strtr($content, $this->embedImagesReplaces);
$tokens = $this->getTokens();
if (preg_match_all('/<img.+?src=[\"\'](.+?)[\"\'].*?>/i', $content, $matches) > 0) {
foreach ($matches[1] as $match) {
// skip items that already embedded, or have token {tracking_pixel}
if (str_contains($match, 'cid:') || str_contains($match, '{tracking_pixel}') || array_key_exists($match, $this->embedImagesReplaces)) {
continue;
}
// skip images with tracking pixel that are already replaced.
if (isset($tokens['{tracking_pixel}']) && $match === $tokens['{tracking_pixel}']) {
continue;
}
$path = $match;
// if the path contains the site url, make it an absolute path, so it can be fetched.
if (str_starts_with($match, $this->coreParametersHelper->get('site_url'))) {
$path = str_replace($this->coreParametersHelper->get('site_url'), '', $match);
$path = $this->factory->getSystemPath('root', true).$path;
}
if ($imageContent = file_get_contents($path)) {
$this->message->embed($imageContent, md5($match));
$this->embedImagesReplaces[$match] = 'cid:'.md5($match);
}
}
$content = strtr($content, $this->embedImagesReplaces);
}
$this->message->html($content);
}
/**
* Get a copy of the raw body.
*
* @return mixed
*/
public function getBody()
{
return $this->body['content'];
}
/**
* Return the content identifier.
*
* @return string
*/
public function getContentHash()
{
return $this->contentHash;
}
/**
* Set to address(es).
*
* @return bool
*/
public function setTo($addresses, $name = null)
{
$name = $this->cleanName($name);
if (!is_array($addresses)) {
$addresses = [$addresses => $name];
} elseif (0 === array_keys($addresses)[0]) {
// We need an array of $email => $name pairs
$addresses = array_reduce($addresses, function ($address, $item) use ($name) {
$address[$item] = $name;
return $address;
}, []);
}
$this->checkBatchMaxRecipients(count($addresses));
// Convert to array of Address objects
$toAddresses = array_map(fn (string $address, ?string $name): Address => new Address($address, $name ?? ''), array_keys($addresses), $addresses);
try {
$this->message->to(...$toAddresses);
$this->queuedRecipients = array_merge($this->queuedRecipients, $addresses);
return true;
} catch (\Exception $e) {
$this->logError($e, 'to');
return false;
}
}
/**
* Add to address.
*
* @param string $address
* @param string|null $name
*
* @return bool
*/
public function addTo($address, $name = null)
{
$this->checkBatchMaxRecipients();
try {
$this->message->addTo((new AddressDTO($address, $name))->toMailerAddress());
$this->queuedRecipients[$address] = $name;
return true;
} catch (\Exception $e) {
$this->logError($e, 'to');
return false;
}
}
/**
* Set CC address(es).
*
* @param array<string,?string> $addresses
* @param ?string $name
*
* //TODO: there is a bug here, the name is not passed in CC nor in the array of addresses, we do not handle names for CC
*
* @return bool
*/
public function setCc($addresses, $name = null)
{
$this->checkBatchMaxRecipients(count($addresses), 'cc');
try {
$ccAddresses = [];
// The email addresses are stored in the array keys not the values
// The name of the CC is passed in the function and not in the array
foreach ($addresses as $address => $noName) {
$ccAddresses[] = (new AddressDTO($address, $name))->toMailerAddress();
}
$this->message->cc(...$ccAddresses);
return true;
} catch (\Exception $e) {
$this->logError($e, 'cc');
return false;
}
}
/**
* Add cc address.
*
* @param string $address
* @param ?string $name
*
* @return bool
*/
public function addCc($address, $name = null)
{
$this->checkBatchMaxRecipients(1, 'cc');
try {
$this->message->addCc((new AddressDTO($address, $name ?? ''))->toMailerAddress());
return true;
} catch (\Exception $e) {
$this->logError($e, 'cc');
return false;
}
}
/**
* Set BCC address(es).
*
* @param array<string,?string> $addresses
* @param ?string $name
*
* //TODO: same bug for the name as the one we have in setCc
*
* @return bool
*/
public function setBcc($addresses, $name = null)
{
$this->checkBatchMaxRecipients(count($addresses), 'bcc');
try {
$bccAddresses = [];
// The email addresses are stored in the array keys not the values
// The name of the Bcc is passed in the function and not in the array
foreach ($addresses as $address => $noName) {
$bccAddresses[] = (new AddressDTO($address, $name))->toMailerAddress();
}
$this->message->bcc(...$bccAddresses);
return true;
} catch (\Exception $e) {
$this->logError($e, 'bcc');
return false;
}
}
/**
* Add bcc address.
*
* @param string $address
* @param ?string $name
*
* @return bool
*/
public function addBcc($address, $name = null)
{
$this->checkBatchMaxRecipients(1, 'bcc');
try {
$this->message->addBcc((new AddressDTO($address, $name))->toMailerAddress());
return true;
} catch (\Exception $e) {
$this->logError($e, 'bcc');
return false;
}
}
/**
* @param int $toBeAdded
* @param string $type
*
* @throws BatchQueueMaxException
*/
protected function checkBatchMaxRecipients($toBeAdded = 1, $type = 'to')
{
if ($this->queueEnabled && $this->transport instanceof TokenTransportInterface) {
// Check if max batching has been hit
$maxAllowed = $this->transport->getMaxBatchLimit();
if ($maxAllowed > 0) {
$currentCount = $this->transport->getBatchRecipientCount($this->message, $toBeAdded, $type);
if ($currentCount > $maxAllowed) {
throw new BatchQueueMaxException();
}
}
}
}
/**
* Set reply to address(es) for this mailer instance.
*
* @param array<string>|string $addresses
* @param string $name
*/
public function setReplyTo($addresses, $name = null): void
{
$this->replyTo = $addresses;
}
/**
* Set Reply to for the current message we are sending. Can be in the middle of the sending loop.
*/
private function setMessageReplyTo(string $addresses, string $name = null): void
{
if (str_contains($addresses, ',')) {
$addresses = explode(',', $addresses);
}
try {
foreach ((array) $addresses as $address) {
$this->message->replyTo((new AddressDTO($address, $name))->toMailerAddress());
}
} catch (\Exception $e) {
$this->logError($e, 'reply to');
}
}
/**
* Set a custom return path.
*
* @param string $address
*/
public function setReturnPath($address): void
{
try {
$this->message->returnPath($address);
} catch (\Exception $e) {
$this->logError($e, 'return path');
}
}
/**
* Sets FROM for the mailer which can overwrite the system default.
*
* @param string|array $fromEmail
* @param string $fromName
*/
public function setFrom($fromEmail, $fromName = null): void
{
if (is_array($fromEmail)) {
$this->from = AddressDTO::fromAddressArray($fromEmail);
$this->from->setName($fromName);
} else {
$this->from = new AddressDTO($fromEmail, $fromName);
}
}
/**
* Sets FROM for the concreste message that we are currently sending. Can be in the middle of the loop of sending.
*/
private function setMessageFrom(AddressDTO $from): void
{
try {
$this->message->from($from->toMailerAddress());
} catch (\Exception $e) {
$this->logError($e, 'from');
}
}
/**
* @return string|null
*/
public function getIdHash()
{
return $this->idHash;
}
/**
* @param string|null $idHash
* @param bool $statToBeGenerated Pass false if a stat entry is not to be created
*/
public function setIdHash($idHash = null, $statToBeGenerated = true): void
{
if (null === $idHash) {
$idHash = str_replace('.', '', uniqid('', true));
}
$this->idHash = $idHash;
$this->idHashState = $statToBeGenerated;
// Append pixel to body before send
$this->appendTrackingPixel = true;
// Add the trackingID to the $message object in order to update the stats if the email failed to send
$this->message->updateLeadIdHash($idHash);
}
/**
* @return array|Lead
*/
public function getLead()
{
return $this->lead;
}
/**
* @param array|Lead $lead
*/
public function setLead($lead, $interalSend = false): void
{
$this->lead = $lead;
$this->internalSend = $interalSend;
}
/**
* Check if this is not being send directly to the lead.
*
* @return bool
*/
public function isInternalSend()
{
return $this->internalSend;
}
/**
* @return array
*/
public function getSource()
{
return $this->source;
}
/**
* @param array $source
*/
public function setSource($source): void
{
$this->source = $source;
}
public function getEmailType(): ?string
{
return $this->emailType;
}
public function setEmailType(?string $emailType): void
{
$this->emailType = $emailType;
}
/**
* @return Email|null
*/
public function getEmail()
{
return $this->email;
}
/**
* @param bool $allowBcc Honor BCC if set in email
* @param array $slots Slots configured in theme
* @param array $assetAttachments Assets to send
* @param bool $ignoreTrackingPixel Do not append tracking pixel HTML
*
* @return bool Returns false if there were errors with the email configuration
*/
public function setEmail(Email $email, $allowBcc = true, $slots = [], $assetAttachments = [], $ignoreTrackingPixel = false): bool
{
if ($this->coreParametersHelper->get(ConfigType::MINIFY_EMAIL_HTML)) {
$email->setCustomHtml(InputHelper::minifyHTML($email->getCustomHtml()));
}
$this->email = $email;
$subject = $email->getSubject();
// Set message settings from the email
$this->setSubject($subject);
if ($allowBcc) {
$bccAddress = $email->getBccAddress();
if (!empty($bccAddress)) {
$addresses = array_fill_keys(array_map('trim', explode(',', $bccAddress)), null);
foreach ($addresses as $bccAddress => $name) {
$this->addBcc($bccAddress, $name);
}
}
}
if ($plainText = $email->getPlainText()) {
$this->setPlainText($plainText);
}
$template = $email->getTemplate();
$customHtml = $email->getCustomHtml();
// Process emails created by Mautic v1
if (empty($customHtml) && $template) {
if (empty($slots)) {
$slots = $this->factory->getTheme($template)->getSlots('email');
}
if (isset($slots[$template])) {
$slots = $slots[$template];
}
$this->processSlots($slots, $email);
$logicalName = $this->factory->getHelper('theme')->checkForTwigTemplate('@themes/'.$template.'/html/email.html.twig');
$customHtml = $this->setTemplate($logicalName, [
'slots' => $slots,
'content' => $email->getContent(),
'email' => $email,
'template' => $template,
], true);
}
$this->setBody($customHtml, 'text/html', null, $ignoreTrackingPixel);
// Reset attachments
$this->assets = $this->attachedAssets = [];
if (empty($assetAttachments)) {
if ($assets = $email->getAssetAttachments()) {
foreach ($assets as $asset) {
$this->attachAsset($asset);
}
}
} else {
foreach ($assetAttachments as $asset) {
$this->attachAsset($asset);
}
}
// Set custom headers
if ($headers = $email->getHeaders()) {
// HTML decode headers
$headers = array_map('html_entity_decode', $headers);
foreach ($headers as $name => $value) {
$this->addCustomHeader($name, $value);
}
}
return empty($this->errors);
}
/**
* Set custom headers.
*
* @param bool $merge
*/
public function setCustomHeaders(array $headers, $merge = true): void
{
if ($merge) {
$this->headers = array_merge($this->headers, $headers);
return;
}
$this->headers = $headers;
}
public function addCustomHeader($name, $value): void
{
$this->headers[$name] = $value;
}
public function getCustomHeaders(): array
{
$headers = array_merge($this->headers, $this->getSystemHeaders());
// Personal and transactional emails do not contain unsubscribe header
$email = $this->getEmail();
if (empty($email) || self::EMAIL_TYPE_TRANSACTIONAL === $this->getEmailType()) {
return $headers;
}
$listUnsubscribeHeader = $this->getUnsubscribeHeader();
if ($listUnsubscribeHeader) {
if (!empty($headers['List-Unsubscribe'])) {
if (!str_contains($headers['List-Unsubscribe'], $listUnsubscribeHeader)) {
// Ensure Mautic's is always part of this header
$headers['List-Unsubscribe'] = $listUnsubscribeHeader.','.$headers['List-Unsubscribe'];
}
} else {
$headers['List-Unsubscribe'] = $listUnsubscribeHeader;
}
$headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
}
return $headers;
}
/**
* @return bool|string
*/
private function getUnsubscribeHeader()
{
if ($this->idHash) {
$lead = $this->getLead();
$toEmail = null;
if (is_array($lead) && array_key_exists('email', $lead) && is_string($lead['email'])) {
$toEmail = $lead['email'];
} elseif ($lead instanceof Lead && is_string($lead->getEmail())) {
$toEmail = $lead->getEmail();
}
if ($toEmail) {
$unsubscribeHash = $this->mailHashHelper->getEmailHash($toEmail);
$url = $this->router->generate('mautic_email_unsubscribe',
['idHash' => $this->idHash, 'urlEmail' => $toEmail, 'secretHash' => $unsubscribeHash],
UrlGeneratorInterface::ABSOLUTE_URL
);
} else {
$url = $this->router->generate('mautic_email_unsubscribe',
['idHash' => $this->idHash],
UrlGeneratorInterface::ABSOLUTE_URL
);
}
return "<$url>";
}
if (!empty($this->queuedRecipients) || !empty($this->lead)) {
return '<{unsubscribe_url}>';
}
return false;
}
/**
* Append tokens.
*/
public function addTokens(array $tokens): void
{
$this->globalTokens = array_merge($this->globalTokens, $tokens);
}
public function setTokens(array $tokens): void
{
$this->globalTokens = $tokens;
}
/**
* @return mixed[]
*/
public function getTokens(): array
{
$tokens = array_merge($this->globalTokens, $this->eventTokens);
// Include the tracking pixel token as it's auto appended to the body
if ($this->appendTrackingPixel) {
$tokens['{tracking_pixel}'] = $this->router->generate(
'mautic_email_tracker',
[
'idHash' => $this->idHash,
],
UrlGeneratorInterface::ABSOLUTE_URL
);
} else {
$tokens['{tracking_pixel}'] = self::getBlankPixel();
}
return $tokens;
}
/**
* @return array
*/
public function getGlobalTokens()
{
return $this->globalTokens;
}
/**
* Parses html into basic plaintext.
*
* @param string $content
*/
public function parsePlainText($content = null): void
{
if (null == $content) {
if (!$content = $this->message->getHtmlBody()) {
$content = $this->body['content'];
}
}
$request = $this->factory->getRequest();
$parser = new PlainTextHelper([
'base_url' => $request->getSchemeAndHttpHost().$request->getBasePath(),
]);
$this->plainText = $parser->setHtml($content)->getText();
}
/**
* Enables queue mode if the transport supports tokenization.
*
* @param bool $enabled
*/
public function enableQueue($enabled = true): void
{
if ($this->tokenizationEnabled) {
$this->queueEnabled = $enabled;
}
}
/**
* Dispatch send event to generate tokens.
*/
public function dispatchSendEvent(): void
{
if (null == $this->dispatcher) {
$this->dispatcher = $this->factory->getDispatcher();
}
$event = new EmailSendEvent($this);
$this->dispatcher->dispatch($event, EmailEvents::EMAIL_ON_SEND);
$this->eventTokens = array_merge($this->eventTokens, $event->getTokens(false));
unset($event);
}
/**
* Log exception.
*/
protected function logError($error, $context = null)
{
if ($error instanceof \Exception) {
$exceptionContext = ['exception' => $error];
$errorMessage = $error->getMessage();
$error = ('dev' === MAUTIC_ENV) ? (string) $error : $errorMessage;
// Clean up the error message
$errorMessage = trim(preg_replace('/(.*?)Log data:(.*)$/is', '$1', $errorMessage));
$this->fatal = true;
} else {
$exceptionContext = [];
$errorMessage = trim($error);
}
if ($context) {
$error .= " ($context)";
if ('send' === $context) {
$error .= '; '.implode(', ', $this->errors['failures']);
}
}
$this->errors[] = $errorMessage;
$this->logger->log('error', '[MAIL ERROR] '.$error, $exceptionContext);
}
/**
* Get list of errors.
*
* @param bool $reset Resets the error array in preparation for the next mail send or else it'll fail
*
* @return array
*/
public function getErrors($reset = true)
{
$errors = $this->errors;
if ($reset) {
$this->clearErrors();
}
return $errors;
}
/**
* Clears the errors from a previous send.
*/
public function clearErrors(): void
{
$this->errors = [];
$this->fatal = false;
}
/**
* @return TransportInterface
*/
public function getTransport()
{
$reflectedMailer = new \ReflectionClass($this->mailer);
$reflectedTransports = $reflectedMailer->getProperty('transport');
$reflectedTransports->setAccessible(true);
$allTransports = $reflectedTransports->getValue($this->mailer);
$reflectedTransports = new \ReflectionClass($allTransports);
$reflectedTransport = $reflectedTransports->getProperty('transports');
$reflectedTransport->setAccessible(true);
$currentTransport = $reflectedTransport->getValue($allTransports);
return $currentTransport['main'];
}
/**
* Creates a download stat for the asset.
*/
protected function createAssetDownloadEntries()
{
// Nothing was sent out so bail
if ($this->fatal || empty($this->assetStats)) {
return;
}
if (isset($this->errors['failures'])) {
// Remove the failures from the asset queue
foreach ($this->errors['failures'] as $failed) {
unset($this->assetStats[$failed]);
}
}
// Create a download entry if there is an Asset attachment
if (!empty($this->assetStats)) {
/** @var \Mautic\AssetBundle\Model\AssetModel $assetModel */
$assetModel = $this->factory->getModel('asset');
foreach ($this->assets as $asset) {
foreach ($this->assetStats as $stat) {
$assetModel->trackDownload(
$asset,
null,
200,
$stat
);
}
$assetModel->upDownloadCount($asset, count($this->assetStats), true);
}
}
// Reset the stat
$this->assetStats = [];
}
/**
* Queues the details to note if a lead received an asset if no errors are generated.
*/
protected function queueAssetDownloadEntry($contactEmail = null, array $metadata = null)
{
if ($this->internalSend || empty($this->assets)) {
return;
}
if (null === $contactEmail) {
if (!$this->lead) {
return;
}
$contactEmail = $this->lead['email'];
$contactId = $this->lead['id'];
$emailId = $this->email->getId();
$idHash = $this->idHash;
} else {
$contactId = $metadata['leadId'];
$emailId = $metadata['emailId'];
$idHash = $metadata['hashId'];
}
$this->assetStats[$contactEmail] = [
'lead' => $contactId,
'email' => $emailId,
'source' => ['email', $emailId],
'tracking_id' => $idHash,
];
}
/**
* Returns if the mailer supports and is in tokenization mode.
*
* @return bool
*/
public function inTokenizationMode()
{
return $this->tokenizationEnabled;
}
/**
* @return \Mautic\PageBundle\Entity\Redirect|object|null
*/
public function getTrackableLink($url)
{
// Ensure a valid URL and that it has not already been found
if (!str_starts_with($url, 'http') && !str_starts_with($url, 'ftp')) {
return null;
}
if ($this->email) {
// Get a Trackable which is channel aware
/** @var \Mautic\PageBundle\Model\TrackableModel $trackableModel */
$trackableModel = $this->factory->getModel('page.trackable');
$trackable = $trackableModel->getTrackableByUrl($url, 'email', $this->email->getId());
return $trackable->getRedirect();
}
/** @var \Mautic\PageBundle\Model\RedirectModel $redirectModel */
$redirectModel = $this->factory->getModel('page.redirect');
return $redirectModel->getRedirectByUrl($url);
}
/**
* Create an email stat.
*
* @param bool|true $persist
* @param string|null $emailAddress
*/
public function createEmailStat($persist = true, $emailAddress = null, $listId = null): Stat
{
$stat = new Stat();
$stat->setDateSent(new \DateTime());
$emailExists = $this->email && $this->email->getId();
if ($emailExists) {
$stat->setEmail($this->email);
}
// Note if a lead
if (null !== $this->lead) {
try {
$stat->setLead($this->factory->getEntityManager()->getReference(Lead::class, $this->lead['id']));
} catch (ORMException) {
// keep IDE happy
}
$emailAddress = $this->lead['email'];
}
// Find email if applicable
if (null === $emailAddress) {
// Use the last address set
$emailAddresses = $this->message->getTo();
if (count($emailAddresses)) {
$emailAddress = array_key_last($emailAddresses);
}
}
$stat->setEmailAddress($emailAddress);
// Note if sent from a lead list
if (null !== $listId) {
try {
$stat->setList($this->factory->getEntityManager()->getReference(\Mautic\LeadBundle\Entity\LeadList::class, $listId));
} catch (ORMException) {
// keep IDE happy
}
}
$stat->setTrackingHash($this->idHash);
if (!empty($this->source)) {
$stat->setSource($this->source[0]);
$stat->setSourceId($this->source[1]);
}
$stat->setTokens($this->getTokens());
/** @var \Mautic\EmailBundle\Model\EmailModel $emailModel */
$emailModel = $this->factory->getModel('email');
// Save a copy of the email - use email ID if available simply to prevent from having to rehash over and over
$id = $emailExists ? $this->email->getId() : md5($this->subject.$this->body['content']);
if (!isset($this->copies[$id])) {
$hash = (32 !== strlen($id)) ? md5($this->subject.$this->body['content']) : $id;
$copy = $emailModel->getCopyRepository()->findByHash($hash);
$copyCreated = false;
if (null === $copy) {
$contentToPersist = strtr($this->body['content'], array_flip($this->embedImagesReplaces));
if (!$emailModel->getCopyRepository()->saveCopy($hash, $this->subject, $contentToPersist, $this->plainText)) {
// Try one more time to find the ID in case there was overlap when creating
$copy = $emailModel->getCopyRepository()->findByHash($hash);
} else {
$copyCreated = true;
}
}
if ($copy || $copyCreated) {
$this->copies[$id] = $hash;
}
}
if (isset($this->copies[$id])) {
try {
$stat->setStoredCopy($this->factory->getEntityManager()->getReference(Copy::class, $this->copies[$id]));
} catch (ORMException) {
// keep IDE happy
}
}
if ($persist) {
$emailModel->saveEmailStat($stat);
}
return $stat;
}
/**
* Check to see if a monitored email box is enabled and configured.
*
* @return bool|array
*/
public function isMontoringEnabled($bundleKey, $folderKey)
{
if ($this->mailbox->isConfigured($bundleKey, $folderKey)) {
return $this->mailbox->getMailboxSettings();
}
return false;
}
/**
* Generate bounce email for the lead.
*
* @return bool|string
*/
public function generateBounceEmail($idHash = null)
{
$monitoredEmail = false;
if ($settings = $this->isMontoringEnabled('EmailBundle', 'bounces')) {
// Append the bounce notation
[$email, $domain] = explode('@', $settings['address']);
$email .= '+bounce';
if ($idHash || $this->idHash) {
$email .= '_'.($idHash ?: $this->idHash);
}
$monitoredEmail = $email.'@'.$domain;
}
return $monitoredEmail;
}
/**
* Generate an unsubscribe email for the lead.
*
* @return bool|string
*/
public function generateUnsubscribeEmail($idHash = null)
{
$monitoredEmail = false;
if ($settings = $this->isMontoringEnabled('EmailBundle', 'unsubscribes')) {
// Append the bounce notation
[$email, $domain] = explode('@', $settings['address']);
$email .= '+unsubscribe';
if ($idHash || $this->idHash) {
$email .= '_'.($idHash ?: $this->idHash);
}
$monitoredEmail = $email.'@'.$domain;
}
return $monitoredEmail;
}
/**
* @param Email $entity
*/
public function processSlots($slots, $entity): void
{
/** @var \Mautic\CoreBundle\Twig\Helper\SlotsHelper $slotsHelper */
$slotsHelper = $this->factory->getHelper('template.slots');
$content = $entity->getContent();
foreach ($slots as $slot => $slotConfig) {
if (is_numeric($slot)) {
$slot = $slotConfig;
$slotConfig = [];
}
$value = $content[$slot] ?? '';
$slotsHelper->set($slot, $value);
}
}
/**
* Clean the name - if empty, set as null to ensure pretty headers.
*
* @return string|null
*/
protected function cleanName($name)
{
if (null === $name) {
return $name;
}
$name = trim(html_entity_decode($name, ENT_QUOTES));
// If empty, replace with null so that email clients do not show empty name because of To: '' <[email protected]>
if (empty($name)) {
$name = null;
}
return $name;
}
/**
* @return array<string,string>
*/
private function getSystemHeaders(): array
{
/**
* This section is stopped, because it is preventing global headers from being merged
* if ($this->email) {
* // We are purposively ignoring system headers if using an Email entity
* return [];
* }.
*/
if (!$systemHeaders = $this->coreParametersHelper->get('mailer_custom_headers', [])) {
return [];
}
// HTML decode headers
$systemHeaders = array_map('html_entity_decode', $systemHeaders);
return $systemHeaders;
}
/**
* Merge system headers into custom headers if applicable.
*/
private function setMessageHeaders(): void
{
$headers = $this->getCustomHeaders();
// Set custom headers
if (!empty($headers)) {
$tokens = $this->getTokens();
// Replace tokens
$messageHeaders = $this->message->getHeaders();
foreach ($headers as $headerKey => $headerValue) {
$headerValue = str_ireplace(array_keys($tokens), $tokens, $headerValue);
if (!$headerValue) {
$messageHeaders->remove($headerKey);
continue;
}
try {
if (in_array(strtolower($headerKey), ['from', 'to', 'cc', 'bcc', 'reply-to'])) {
// Handling headers that require MailboxListHeader
$headerValue = array_map(fn ($address): Address => new Address($address),
explode(',', $headerValue));
}
if ($messageHeaders->has($headerKey)) {
$header = $messageHeaders->get($headerKey);
$header->setBody($headerValue);
} else {
$messageHeaders->addHeader($headerKey, $headerValue);
}
} catch (RfcComplianceException) {
$messageHeaders->remove($headerKey);
}
}
}
if (array_key_exists('List-Unsubscribe', $headers)) {
unset($headers['List-Unsubscribe']);
$this->setCustomHeaders($headers, false);
}
}
private function buildMetadata($name, array $tokens): array
{
return [
'name' => $name,
'leadId' => (!empty($this->lead)) ? $this->lead['id'] : null,
'emailId' => (!empty($this->email)) ? $this->email->getId() : null,
'emailName' => (!empty($this->email)) ? $this->email->getName() : null,
'hashId' => $this->idHash,
'hashIdState' => $this->idHashState,
'source' => $this->source,
'tokens' => $tokens,
'utmTags' => (!empty($this->email)) ? $this->email->getUtmTags() : [],
];
}
/**
* Validates a given address to ensure RFC 2822, 3.6.2 specs.
*
* @deprecated 2.11.0 to be removed in 3.0; use Mautic\EmailBundle\Helper\EmailValidator
*
* @throws InvalidEmailException
*/
public static function validateEmail($address): void
{
$invalidChar = strpbrk($address, '\'^&*%');
if (false !== $invalidChar) {
throw new InvalidEmailException('Email address ['.$address.'] contains this invalid character: '.substr($invalidChar, 0, 1));
}
if (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailException('Email address ['.$address.'] is invalid');
}
}
private function setDefaultFrom($overrideFrom, AddressDTO $systemFrom): void
{
if (is_array($overrideFrom)) {
$fromEmail = key($overrideFrom);
$fromName = $this->cleanName($overrideFrom[$fromEmail]);
$overrideFrom = [$fromEmail => $fromName];
} elseif (!empty($overrideFrom)) {
$overrideFrom = [$overrideFrom => null];
}
$this->systemFrom = $overrideFrom ?: $systemFrom;
$this->from = $this->systemFrom;
}
private function setDefaultReplyTo($systemReplyToEmail = null, AddressDTO $systemFromEmail = null): void
{
$fromEmail = null;
if ($systemFromEmail) {
$fromEmail = $systemFromEmail->getEmail();
}
$this->systemReplyTo = $systemReplyToEmail ?: $fromEmail;
$this->replyTo = $this->systemReplyTo;
}
private function setFromForSingleMessage(): void
{
$email = $this->getEmail();
if ($this->lead && $email && $email->getUseOwnerAsMailer()) {
if (!isset($this->lead['owner_id'])) {
$this->lead['owner_id'] = 0;
}
$from = $this->fromEmailHelper->getFromAddressConsideringOwner($this->getFrom(), $this->lead, $email);
$this->setMessageFrom($from);
return;
}
if ($email) {
$fromEmail = $email->getFromAddress();
$fromName = $email->getFromName();
if (!empty($fromEmail) || !empty($fromName)) {
if (empty($fromName)) {
$fromName = $this->getFrom()->getName();
} elseif (empty($fromEmail)) {
$fromEmail = $this->getFrom()->getEmail();
}
$this->from = new AddressDTO($fromEmail, $fromName);
}
}
$from = $this->fromEmailHelper->getFromAddressDto($this->getFrom(), $this->lead, $email);
$this->setMessageFrom($from);
}
private function setReplyToForSingleMessage(?Email $emailToSend): void
{
// 1. Set the reply to address from the email "reply-to" setting if set.
if ($emailToSend && null !== $emailToSend->getReplyToAddress()) {
$this->setMessageReplyTo($emailToSend->getReplyToAddress());
return;
}
// 2. Set the reply to address from the lead owner if set.
if (!empty($this->lead['owner_id'])) {
try {
$owner = $this->fromEmailHelper->getContactOwner((int) $this->lead['owner_id'], $emailToSend);
$this->setMessageReplyTo($owner['email']);
} catch (OwnerNotFoundException) {
$this->setMessageReplyTo($this->getSystemReplyTo());
}
return;
}
// 3. Set the reply to address from the email "from" setting if set.
if ($emailToSend && null !== $emailToSend->getFromAddress() && empty($this->coreParametersHelper->get('mailer_reply_to_email'))) {
$this->setMessageReplyTo($emailToSend->getFromAddress());
return;
}
// 4. Set the reply to address from the global config if nothing from above is set.
$this->setMessageReplyTo($this->getReplyTo());
}
/**
* @return bool|array
*
* @deprecated
*/
protected function getContactOwner(&$contact)
{
if (!is_array($contact)) {
return false;
}
if (!isset($contact['id'])) {
return false;
}
if (!isset($contact['owner_id'])) {
$contact['owner_id'] = 0;
return false;
}
try {
return $this->fromEmailHelper->getContactOwner($contact['owner_id']);
} catch (OwnerNotFoundException) {
return false;
}
}
/**
* @deprecated; use FromEmailHelper::getUserSignature
*/
protected function getContactOwnerSignature($owner): string
{
if (empty($owner['id'])) {
return '';
}
try {
$this->fromEmailHelper->getContactOwner($owner['id']);
} catch (OwnerNotFoundException) {
return '';
}
return $this->fromEmailHelper->getSignature();
}
private function getMessageInstance(): MauticMessage
{
return new MauticMessage();
}
private function getReplyTo(): string
{
return $this->replyTo ?? $this->getSystemReplyTo();
}
private function getSystemReplyTo(): string
{
if (!$this->systemReplyTo) {
$fromEmailAddress = $this->from ? $this->from->getEmail() : null;
$this->systemReplyTo = $this->coreParametersHelper->get('mailer_reply_to_email') ?? $fromEmailAddress ?? $this->getSystemFrom()->getEmail();
}
return $this->systemReplyTo;
}
private function getFrom(): AddressDTO
{
return $this->from ?? $this->getSystemFrom();
}
private function getSystemFrom(): AddressDTO
{
if (!$this->systemFrom || $this->systemFrom->isEmpty()) {
$this->systemFrom = new AddressDTO($this->coreParametersHelper->get('mailer_from_email'), $this->coreParametersHelper->get('mailer_from_name'));
$this->fromEmailHelper->setDefaultFrom($this->systemFrom);
}
return $this->systemFrom;
}
public function dispatchPreSendEvent(): void
{
if (null === $this->dispatcher) {
$this->dispatcher = $this->factory->getDispatcher();
}
if (empty($this->dispatcher)) {
return;
}
$event = new EmailSendEvent($this);
$this->dispatcher->dispatch($event, EmailEvents::EMAIL_PRE_SEND);
$this->skip = $event->isSkip();
$this->fatal = $event->isFatal();
$errors = $event->getErrors();
if (!empty($errors)) {
$currentErrors = [];
if (isset($this->errors['failures']) && is_array($this->errors['failures'])) {
$currentErrors = $this->errors['failures'];
}
$this->errors['failures'] = array_merge($errors, $currentErrors);
}
unset($event);
}
}