/** * Send an email to lead(s) * * @param $email * @param $leads * @param $options = array() * array source array('model', 'id') * array emailSettings * int listId * bool allowResends If false, exact emails (by id) already sent to the lead will not be resent * bool ignoreDNC If true, emails listed in the do not contact table will still get the email * bool sendBatchMail If false, the function will not send batched mail but will defer to calling function to handle it * array assetAttachments Array of optional Asset IDs to attach * * @return mixed * @throws \Doctrine\ORM\ORMException */ public function sendEmail($email, $leads, $options = array()) { $source = isset($options['source']) ? $options['source'] : null; $emailSettings = isset($options['emailSettings']) ? $options['emailSettings'] : array(); $listId = isset($options['listId']) ? $options['listId'] : null; $ignoreDNC = isset($options['ignoreDNC']) ? $options['ignoreDNC'] : false; $allowResends = isset($options['allowResends']) ? $options['allowResends'] : true; $tokens = isset($options['tokens']) ? $options['tokens'] : array(); $sendBatchMail = isset($options['sendBatchMail']) ? $options['sendBatchMail'] : true; $assetAttachments = isset($options['assetAttachments']) ? $options['assetAttachments'] : array(); $customHeaders = isset($options['customHeaders']) ? $options['customHeaders'] : array(); if (!$email->getId()) { return false; } $singleEmail = false; if (isset($leads['id'])) { $singleEmail = true; $leads = array($leads['id'] => $leads); } /** @var \Mautic\EmailBundle\Entity\StatRepository $statRepo */ $statRepo = $this->em->getRepository('MauticEmailBundle:Stat'); /** @var \Mautic\EmailBundle\Entity\EmailRepository $emailRepo */ $emailRepo = $this->getRepository(); if (empty($emailSettings)) { //get email settings such as templates, weights, etc $emailSettings = $this->getEmailSettings($email); } if (!$allowResends) { static $sent = array(); if (!isset($sent[$email->getId()])) { $sent[$email->getId()] = $statRepo->getSentStats($email->getId(), $listId); } $sendTo = array_diff_key($leads, $sent[$email->getId()]); } else { $sendTo = $leads; } if (!$ignoreDNC) { //get the list of do not contacts static $dnc; if (!is_array($dnc)) { $dnc = $emailRepo->getDoNotEmailList(); } //weed out do not contacts if (!empty($dnc)) { foreach ($sendTo as $k => $lead) { if (in_array(strtolower($lead['email']), $dnc)) { unset($sendTo[$k]); } } } } //get a count of leads $count = count($sendTo); //noone to send to so bail if (empty($count)) { return $singleEmail ? true : array(); } //how many of this batch should go to which email $batchCount = 0; $backup = reset($emailSettings); foreach ($emailSettings as $eid => &$details) { if (isset($details['weight'])) { $limit = round($count * $details['weight']); if (!$limit) { // Don't send any emails to this one unset($emailSettings[$eid]); } else { $details['limit'] = $limit; } } else { $details['limit'] = $count; } } if (count($emailSettings) == 0) { // Shouldn't happen but a safety catch $emailSettings[$backup['entity']->getId()] = $backup; } //randomize the leads for statistic purposes shuffle($sendTo); //start at the beginning for this batch $useEmail = reset($emailSettings); $errors = array(); // Store stat entities $saveEntities = array(); $mailer = $this->factory->getMailer(!$sendBatchMail); $contentGenerated = false; $flushQueue = function ($reset = true) use(&$mailer, &$saveEntities, &$errors, $sendBatchMail) { if ($sendBatchMail) { $flushResult = $mailer->flushQueue(); if (!$flushResult) { $sendFailures = $mailer->getErrors(); // Check to see if failed recipients were stored by the transport if (!empty($sendFailures['failures'])) { // Prevent the stat from saving foreach ($sendFailures['failures'] as $failedEmail) { // Add lead ID to list of failures $errors[$saveEntities[$failedEmail]->getLead()->getId()] = $failedEmail; // Down sent counts $saveEntities[$failedEmail]->getEmail()->downSentCounts(); // Delete the stat unset($saveEntities[$failedEmail]); } } } if ($reset) { $mailer->reset(true); } return $flushResult; } return true; }; foreach ($sendTo as $lead) { // Generate content if ($useEmail['entity']->getId() !== $contentGenerated) { // Flush the mail queue if applicable $flushQueue(); $contentGenerated = $useEmail['entity']->getId(); // Use batching/tokenization if supported $mailer->useMailerTokenization(); $mailer->setSource($source); $mailer->setEmail($useEmail['entity'], true, $useEmail['slots'], $assetAttachments); if (!empty($customHeaders)) { $mailer->setCustomHeaders($customHeaders); } } $idHash = uniqid(); // Add tracking pixel token if (!empty($tokens)) { $mailer->setTokens($tokens); } $mailer->setLead($lead); $mailer->setIdHash($idHash); try { if (!$mailer->addTo($lead['email'], $lead['firstname'] . ' ' . $lead['lastname'])) { // Clear the errors so it doesn't stop the next send $mailer->clearErrors(); // Bad email so note and continue $errors[$lead['id']] = $lead['email']; continue; } } catch (BatchQueueMaxException $e) { // Queue full so flush then try again $flushQueue(false); $mailer->addTo($lead['email'], $lead['firstname'] . ' ' . $lead['lastname']); } //queue or send the message if (!$mailer->queue(true)) { $errors[$lead['id']] = $lead['email']; continue; } if (!$allowResends) { $sent[$useEmail['entity']->getId()][$lead['id']] = $lead['id']; } //create a stat $stat = new Stat(); $stat->setDateSent(new \DateTime()); $stat->setEmail($useEmail['entity']); $stat->setLead($this->em->getReference('MauticLeadBundle:Lead', $lead['id'])); if ($listId) { $stat->setList($this->em->getReference('MauticLeadBundle:LeadList', $listId)); } $stat->setEmailAddress($lead['email']); $stat->setTrackingHash($idHash); if (!empty($source)) { $stat->setSource($source[0]); $stat->setSourceId($source[1]); } $stat->setCopy($mailer->getBody()); $stat->setTokens($mailer->getTokens()); $saveEntities[$lead['email']] = $stat; // Down sent counts $saveEntities[$lead['email']]->getEmail()->upSentCounts(); // Save RAM unset($stat); $batchCount++; if ($batchCount >= $useEmail['limit']) { unset($useEmail); //use the next email $batchCount = 0; $useEmail = next($emailSettings); } } // Send batched mail if applicable $flushQueue(); // Persist stats $statRepo->saveEntities($saveEntities); // Free RAM foreach ($saveEntities as $stat) { $this->em->detach($stat); unset($stat); } unset($emailSettings, $options, $tokens, $useEmail, $sendTo); return $singleEmail ? empty($errors) : $errors; }
/** * Create an email stat * * @param bool|true $persist * @param string|null $emailAddress * @param null $listId * * @return Stat|void * @throws \Doctrine\ORM\ORMException */ public function createEmailStat($persist = true, $emailAddress = null, $listId = null) { static $copies = array(); //create a stat $stat = new Stat(); $stat->setDateSent(new \DateTime()); $stat->setEmail($this->email); // Note if a lead if (null !== $this->lead) { $stat->setLead($this->factory->getEntityManager()->getReference('MauticLeadBundle:Lead', $this->lead['id'])); $emailAddress = $this->lead['email']; } // Find email if applicable if (null === $emailAddress) { // Use the last address set $emailAddresses = $this->message->getTo(); if (count($emailAddresses)) { end($emailAddresses); $emailAddress = key($emailAddresses); } } $stat->setEmailAddress($emailAddress); // Note if sent from a lead list if (null !== $listId) { $stat->setList($this->factory->getEntityManager()->getReference('MauticLeadBundle:LeadList', $listId)); } $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 = null !== $this->email ? $this->email->getId() : md5($this->subject . $this->body['content']); if (!isset($copies[$id])) { $hash = strlen($id) !== 32 ? md5($this->subject . $this->body['content']) : $id; $copy = $emailModel->getCopyRepository()->findByHash($hash); if (null === $copy) { // Create a copy entry $copy = new Copy(); $copy->setId($hash)->setBody($this->body['content'])->setSubject($this->subject)->setDateCreated(new \DateTime())->setEmail($this->email); $emailModel->getCopyRepository()->saveEntity($copy); } $copies[$id] = $copy; } $stat->setStoredCopy($copies[$id]); if ($persist) { $emailModel->getStatRepository()->saveEntity($stat); } return $stat; }
/** * Create an email stat */ public function createLeadEmailStat() { if (!$this->lead) { return; } //create a stat $stat = new Stat(); $stat->setDateSent(new \DateTime()); $stat->setEmail($this->email); $stat->setLead($this->factory->getEntityManager()->getReference('MauticLeadBundle:Lead', $this->lead['id'])); $stat->setEmailAddress($this->lead['email']); $stat->setTrackingHash($this->idHash); if (!empty($this->source)) { $stat->setSource($this->source[0]); $stat->setSourceId($this->source[1]); } $stat->setCopy($this->getBody()); $stat->setTokens($this->getTokens()); /** @var \Mautic\EmailBundle\Model\EmailModel $emailModel */ $emailModel = $this->factory->getModel('email'); $emailModel->getStatRepository()->saveEntity($stat); }
/** * Test that processMailerCallback handles an array of hashIds correctly. */ public function testProcessMailerCallbackWithHashIds() { // Setup dependencies $ipLookupHelper = $this->getMockBuilder(IpLookupHelper::class)->disableOriginalConstructor()->getMock(); $themeHelper = $this->getMockBuilder(ThemeHelper::class)->disableOriginalConstructor()->getMock(); $mailboxHelper = $this->getMockBuilder(Mailbox::class)->disableOriginalConstructor()->getMock(); $mailHelper = $this->getMockBuilder(MailHelper::class)->disableOriginalConstructor()->getMock(); $leadModel = $this->getMockBuilder(LeadModel::class)->disableOriginalConstructor()->getMock(); $trackableModel = $this->getMockBuilder(TrackableModel::class)->disableOriginalConstructor()->getMock(); $userModel = $this->getMockBuilder(UserModel::class)->disableOriginalConstructor()->getMock(); $coreParametersHelper = $this->getMockBuilder(CoreParametersHelper::class)->disableOriginalConstructor()->getMock(); // Setup the translator $translator = $this->getMockBuilder(Translator::class)->disableOriginalConstructor()->getMock(); $translator->expects($this->any())->method('has')->will($this->returnValue(false)); // Setup the StatRepository $statRepository = $this->getMockBuilder(StatRepository::class)->disableOriginalConstructor()->getMock(); $statRepository->expects($this->once())->method('getTableAlias')->will($this->returnValue('s')); $leadEntity = $this->getMockBuilder(Lead::class)->disableOriginalConstructor()->getMock(); $leadEntity->expects($this->any())->method('getId')->will($this->returnValue(1)); $emailEntity = $this->getMockBuilder(Email::class)->disableOriginalConstructor()->getMock(); $emailEntity->expects($this->any())->method('getId')->will($this->returnValue(1)); $statEntity = new Stat(); $statEntity->setTrackingHash('xyz123'); $statEntity->setLead($leadEntity); $statEntity->setEmail($emailEntity); $statRepository->expects($this->any())->method('getEntities')->will($this->returnValue([$statEntity])); // Setup the EntityManager $entityManager = $this->getMockBuilder(EntityManager::class)->disableOriginalConstructor()->getMock(); $entityManager->expects($this->any())->method('getRepository')->will($this->returnValueMap([['MauticEmailBundle:Stat', $statRepository]])); $entityManager->expects($this->any())->method('getReference')->will($this->returnValue($leadEntity)); $messageModel = $this->getMockBuilder(MessageQueueModel::class)->disableOriginalConstructor()->getMock(); $companyModel = $this->getMockBuilder(CompanyModel::class)->disableOriginalConstructor()->getMock(); $companyRepository = $this->getMockBuilder(CompanyRepository::class)->disableOriginalConstructor()->getMock(); $companyRepository->method('getCompaniesForContacts')->will($this->returnValue([])); $companyModel->method('getRepository')->willReturn($companyRepository); $emailModel = new \Mautic\EmailBundle\Model\EmailModel($ipLookupHelper, $themeHelper, $mailboxHelper, $mailHelper, $leadModel, $companyModel, $trackableModel, $userModel, $coreParametersHelper, $messageModel); $emailModel->setTranslator($translator); $emailModel->setEntityManager($entityManager); $response = [2 => ['hashIds' => ['xyz123' => 'some reason', '123xyz' => 'some reason']]]; $dnc = $emailModel->processMailerCallback($response); $this->assertCount(1, $dnc); }
/** * {@inheritDoc} */ public function setLead(\Mautic\LeadBundle\Entity\Lead $lead = NULL) { $this->__initializer__ && $this->__initializer__->__invoke($this, 'setLead', array($lead)); return parent::setLead($lead); }