mautic / app /bundles /EmailBundle /Model /SendEmailToContact.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
namespace Mautic\EmailBundle\Model;
use Mautic\EmailBundle\Entity\Email;
use Mautic\EmailBundle\Exception\FailedToSendToContactException;
use Mautic\EmailBundle\Helper\MailHelper;
use Mautic\EmailBundle\Mailer\Exception\BatchQueueMaxException;
use Mautic\EmailBundle\Stat\Exception\StatNotFoundException;
use Mautic\EmailBundle\Stat\Reference;
use Mautic\EmailBundle\Stat\StatHelper;
use Mautic\LeadBundle\Entity\DoNotContact as DNC;
use Mautic\LeadBundle\Model\DoNotContact;
use Symfony\Contracts\Translation\TranslatorInterface;
class SendEmailToContact
{
/**
* @var string|null
*/
private $singleEmailMode;
private array $failedContacts = [];
/**
* @var array
*/
private $errorMessages = [];
/**
* @var array
*/
private $badEmails = [];
/**
* @var array
*/
private $emailSentCounts = [];
/**
* @var array|null
*/
private $emailEntityErrors;
/**
* @var int|null
*/
private $emailEntityId;
private ?int $listId = null;
private int $statBatchCounter = 0;
/**
* @var array
*/
private $contact = [];
public function __construct(
private MailHelper $mailer,
private StatHelper $statHelper,
private DoNotContact $dncModel,
private TranslatorInterface $translator
) {
}
/**
* @param bool $resetMailer
*
* @return $this
*/
public function flush($resetMailer = true)
{
// Flushes the batch in case of using API mailers
if ($this->emailEntityId && !$flushResult = $this->mailer->flushQueue()) {
$sendFailures = $this->mailer->getErrors();
// Check to see if failed recipients were stored by the transport
if (!empty($sendFailures['failures'])) {
$this->processSendFailures($sendFailures);
} elseif ($this->singleEmailMode) {
$this->errorMessages[$this->singleEmailMode] = implode('; ', $sendFailures);
}
}
if ($resetMailer) {
$this->mailer->reset(true);
}
return $this;
}
/**
* Flush any remaining queued contacts, process spending stats, create DNC entries and reset this class.
*/
public function finalFlush(): void
{
$this->flush();
$this->statHelper->deletePending();
$this->statHelper->reset();
$this->processBadEmails();
}
/**
* Use an Email entity to populate content, from, etc.
*
* @param array $channel ['channelName', 'channelId']
*
* @return $this
*/
public function setEmail(Email $email, array $channel = [], array $customHeaders = [], array $assetAttachments = [], string $emailType = null)
{
// Flush anything that's pending from a previous email
$this->flush();
// Enable the queue if applicable to the transport
$this->mailer->enableQueue();
if ($this->mailer->setEmail($email, true, [], $assetAttachments)) {
$this->mailer->setEmailType($emailType);
$this->mailer->setSource($channel);
$this->mailer->setCustomHeaders($customHeaders);
// Note that the entity is set so that addContact does not generate errors
$this->emailEntityId = $email->getId();
} else {
// Fail all the contacts in this batch
$this->emailEntityErrors = $this->mailer->getErrors();
$this->emailEntityId = null;
}
return $this;
}
/**
* @param int|null $id
*
* @return $this
*/
public function setListId($id)
{
$this->listId = empty($id) ? null : (int) $id;
return $this;
}
/**
* @return $this
*
* @throws FailedToSendToContactException
*/
public function setContact(array $contact, array $tokens = [])
{
$this->contact = $contact;
if (!$this->emailEntityId) {
// There was an error configuring the email so auto fail
$this->failContact(false, $this->emailEntityErrors);
}
$this->mailer->setTokens($tokens);
$this->mailer->setLead($contact);
$this->mailer->setIdHash(); // auto generates
try {
if (!$this->mailer->addTo($contact['email'], $contact['firstname'].' '.$contact['lastname'])) {
$this->failContact();
}
} catch (BatchQueueMaxException) {
// Queue full so flush then try again
$this->flush(false);
if (!$this->mailer->addTo($contact['email'], $contact['firstname'].' '.$contact['lastname'])) {
$this->failContact();
}
}
return $this;
}
/**
* @throws FailedToSendToContactException
*/
public function send(): void
{
if ($this->mailer->inTokenizationMode()) {
[$success, $errors] = $this->queueTokenizedEmail();
} else {
[$success, $errors] = $this->sendStandardEmail();
}
// queue or send the message
if (!$success) {
unset($errors['failures']);
$this->failContact(false, implode('; ', (array) $errors));
}
}
/**
* Reset everything.
*/
public function reset(): void
{
$this->badEmails = [];
$this->errorMessages = [];
$this->failedContacts = [];
$this->emailEntityErrors = null;
$this->emailEntityId = null;
$this->emailSentCounts = [];
$this->singleEmailMode = null;
$this->listId = null;
$this->statBatchCounter = 0;
$this->contact = [];
$this->mailer->reset();
}
/**
* @return array
*/
public function getSentCounts()
{
return $this->emailSentCounts;
}
/**
* @return array
*/
public function getErrors()
{
return $this->errorMessages;
}
/**
* @return array
*/
public function getFailedContacts()
{
return $this->failedContacts;
}
/**
* @param bool $hasBadEmail
* @param array $errorMessages
*
* @throws FailedToSendToContactException
*/
protected function failContact($hasBadEmail = true, $errorMessages = null)
{
if (null === $errorMessages) {
// Clear the errors so it doesn't stop the next send
$errorMessages = implode('; ', (array) $this->mailer->getErrors());
} elseif (is_array($errorMessages)) {
$errorMessages = implode('; ', $errorMessages);
}
$this->errorMessages[$this->contact['id']] = $errorMessages;
$this->failedContacts[$this->contact['id']] = $this->contact['email'];
try {
$stat = $this->statHelper->getStat($this->contact['email']);
$this->downEmailSentCount($stat->getEmailId());
$this->statHelper->markForDeletion($stat);
} catch (StatNotFoundException) {
}
if ($hasBadEmail) {
$this->badEmails[$this->contact['id']] = $this->contact['email'];
}
throw new FailedToSendToContactException($errorMessages);
}
protected function processSendFailures($sendFailures)
{
$failedEmailAddresses = $sendFailures['failures'];
unset($sendFailures['failures']);
$error = implode('; ', $sendFailures);
// Delete the stat
foreach ($failedEmailAddresses as $failedEmail) {
try {
/** @var Reference $stat */
$stat = $this->statHelper->getStat($failedEmail);
} catch (StatNotFoundException) {
continue;
}
// Add lead ID to list of failures
$this->failedContacts[$stat->getLeadId()] = $failedEmail;
$this->errorMessages[$stat->getLeadId()] = $error;
$this->statHelper->markForDeletion($stat);
// Down sent counts
$this->downEmailSentCount($stat->getEmailId());
}
}
/**
* Add DNC entries for bad emails to get them out of the queue permanently.
*/
protected function processBadEmails()
{
// Update bad emails as bounces
if (count($this->badEmails)) {
foreach ($this->badEmails as $contactId => $contactEmail) {
$this->dncModel->addDncForContact(
$contactId,
['email' => $this->emailEntityId],
DNC::BOUNCED,
$this->translator->trans('mautic.email.bounce.reason.bad_email'),
true,
false
);
}
}
}
protected function createContactStatEntry($email)
{
++$this->statBatchCounter;
$stat = $this->mailer->createEmailStat(false, null, $this->listId);
// Store it in the statEntities array so that the stat can be deleted if the transport fails the
// send for whatever reason after flushing the queue
$this->statHelper->storeStat($stat, $email);
$this->upEmailSentCount($stat->getEmail()->getId());
}
/**
* Up sent counter for the given email ID.
*/
protected function upEmailSentCount($emailId)
{
// Up sent counts
if (!isset($this->emailSentCounts[$emailId])) {
$this->emailSentCounts[$emailId] = 0;
}
++$this->emailSentCounts[$emailId];
}
/**
* Down sent counter for the given email ID.
*/
protected function downEmailSentCount($emailId)
{
--$this->emailSentCounts[$emailId];
}
protected function queueTokenizedEmail(): array
{
[$queued, $queueErrors] = $this->mailer->queue(true, MailHelper::QUEUE_RETURN_ERRORS);
if ($queued) {
// Create stat first to ensure it is available for emails sent immediately
$this->createContactStatEntry($this->contact['email']);
}
return [$queued, $queueErrors];
}
/**
* @return array
*/
protected function sendStandardEmail()
{
// Dispatch the event to generate the tokens
$this->mailer->dispatchSendEvent();
// Create the stat to ensure it is available for emails sent
$this->createContactStatEntry($this->contact['email']);
// Now send but don't redispatch the event
return $this->mailer->queue(false, MailHelper::QUEUE_RETURN_ERRORS);
}
}