1 | <?php |
||
2 | |||
3 | /* |
||
4 | * @copyright 2014 Mautic Contributors. All rights reserved |
||
5 | * @author Mautic |
||
6 | * |
||
7 | * @link http://mautic.org |
||
8 | * |
||
9 | * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
||
10 | */ |
||
11 | |||
12 | namespace Mautic\EmailBundle\Model; |
||
13 | |||
14 | use Doctrine\DBAL\Query\QueryBuilder; |
||
15 | use Mautic\ChannelBundle\Entity\MessageQueue; |
||
16 | use Mautic\ChannelBundle\Model\MessageQueueModel; |
||
17 | use Mautic\CoreBundle\Doctrine\Provider\GeneratedColumnsProviderInterface; |
||
18 | use Mautic\CoreBundle\Helper\ArrayHelper; |
||
19 | use Mautic\CoreBundle\Helper\CacheStorageHelper; |
||
20 | use Mautic\CoreBundle\Helper\Chart\BarChart; |
||
21 | use Mautic\CoreBundle\Helper\Chart\ChartQuery; |
||
22 | use Mautic\CoreBundle\Helper\Chart\LineChart; |
||
23 | use Mautic\CoreBundle\Helper\Chart\PieChart; |
||
24 | use Mautic\CoreBundle\Helper\DateTimeHelper; |
||
25 | use Mautic\CoreBundle\Helper\IpLookupHelper; |
||
26 | use Mautic\CoreBundle\Helper\ThemeHelper; |
||
27 | use Mautic\CoreBundle\Model\AjaxLookupModelInterface; |
||
28 | use Mautic\CoreBundle\Model\BuilderModelTrait; |
||
29 | use Mautic\CoreBundle\Model\FormModel; |
||
30 | use Mautic\CoreBundle\Model\TranslationModelTrait; |
||
31 | use Mautic\CoreBundle\Model\VariantModelTrait; |
||
32 | use Mautic\EmailBundle\EmailEvents; |
||
33 | use Mautic\EmailBundle\Entity\Email; |
||
34 | use Mautic\EmailBundle\Entity\Stat; |
||
35 | use Mautic\EmailBundle\Entity\StatDevice; |
||
36 | use Mautic\EmailBundle\Event\EmailBuilderEvent; |
||
37 | use Mautic\EmailBundle\Event\EmailEvent; |
||
38 | use Mautic\EmailBundle\Event\EmailOpenEvent; |
||
39 | use Mautic\EmailBundle\Event\EmailSendEvent; |
||
40 | use Mautic\EmailBundle\Exception\EmailCouldNotBeSentException; |
||
41 | use Mautic\EmailBundle\Exception\FailedToSendToContactException; |
||
42 | use Mautic\EmailBundle\Form\Type\EmailType; |
||
43 | use Mautic\EmailBundle\Helper\MailHelper; |
||
44 | use Mautic\EmailBundle\MonitoredEmail\Mailbox; |
||
45 | use Mautic\LeadBundle\Entity\DoNotContact; |
||
46 | use Mautic\LeadBundle\Entity\Lead; |
||
47 | use Mautic\LeadBundle\Model\CompanyModel; |
||
48 | use Mautic\LeadBundle\Model\DoNotContact as DNC; |
||
49 | use Mautic\LeadBundle\Model\LeadModel; |
||
50 | use Mautic\LeadBundle\Tracker\ContactTracker; |
||
51 | use Mautic\LeadBundle\Tracker\DeviceTracker; |
||
52 | use Mautic\PageBundle\Entity\RedirectRepository; |
||
53 | use Mautic\PageBundle\Model\TrackableModel; |
||
54 | use Mautic\UserBundle\Model\UserModel; |
||
55 | use Symfony\Component\Console\Helper\ProgressBar; |
||
56 | use Symfony\Component\Console\Output\OutputInterface; |
||
57 | use Symfony\Component\EventDispatcher\Event; |
||
58 | use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; |
||
59 | |||
60 | class EmailModel extends FormModel implements AjaxLookupModelInterface |
||
61 | { |
||
62 | use VariantModelTrait; |
||
63 | use TranslationModelTrait; |
||
64 | use BuilderModelTrait; |
||
65 | |||
66 | /** |
||
67 | * @var IpLookupHelper |
||
68 | */ |
||
69 | protected $ipLookupHelper; |
||
70 | |||
71 | /** |
||
72 | * @var ThemeHelper |
||
73 | */ |
||
74 | protected $themeHelper; |
||
75 | |||
76 | /** |
||
77 | * @var Mailbox |
||
78 | */ |
||
79 | protected $mailboxHelper; |
||
80 | |||
81 | /** |
||
82 | * @var MailHelper |
||
83 | */ |
||
84 | protected $mailHelper; |
||
85 | |||
86 | /** |
||
87 | * @var LeadModel |
||
88 | */ |
||
89 | protected $leadModel; |
||
90 | |||
91 | /** |
||
92 | * @var CompanyModel |
||
93 | */ |
||
94 | protected $companyModel; |
||
95 | |||
96 | /** |
||
97 | * @var TrackableModel |
||
98 | */ |
||
99 | protected $pageTrackableModel; |
||
100 | |||
101 | /** |
||
102 | * @var UserModel |
||
103 | */ |
||
104 | protected $userModel; |
||
105 | |||
106 | /** |
||
107 | * @var MessageQueueModel |
||
108 | */ |
||
109 | protected $messageQueueModel; |
||
110 | |||
111 | /** |
||
112 | * @var bool |
||
113 | */ |
||
114 | protected $updatingTranslationChildren = false; |
||
115 | |||
116 | /** |
||
117 | * @var array |
||
118 | */ |
||
119 | protected $emailSettings = []; |
||
120 | |||
121 | /** |
||
122 | * @var SendEmailToContact |
||
123 | */ |
||
124 | protected $sendModel; |
||
125 | |||
126 | /** |
||
127 | * @var DeviceTracker |
||
128 | */ |
||
129 | private $deviceTracker; |
||
130 | |||
131 | /** |
||
132 | * @var RedirectRepository |
||
133 | */ |
||
134 | private $redirectRepository; |
||
135 | |||
136 | /** |
||
137 | * @var CacheStorageHelper |
||
138 | */ |
||
139 | private $cacheStorageHelper; |
||
140 | |||
141 | /** |
||
142 | * @var ContactTracker |
||
143 | */ |
||
144 | private $contactTracker; |
||
145 | |||
146 | /** |
||
147 | * @var DNC |
||
148 | */ |
||
149 | private $doNotContact; |
||
150 | |||
151 | /** |
||
152 | * @var GeneratedColumnsProviderInterface |
||
153 | */ |
||
154 | private $generatedColumnsProvider; |
||
155 | |||
156 | public function __construct( |
||
157 | IpLookupHelper $ipLookupHelper, |
||
158 | ThemeHelper $themeHelper, |
||
159 | Mailbox $mailboxHelper, |
||
160 | MailHelper $mailHelper, |
||
161 | LeadModel $leadModel, |
||
162 | CompanyModel $companyModel, |
||
163 | TrackableModel $pageTrackableModel, |
||
164 | UserModel $userModel, |
||
165 | MessageQueueModel $messageQueueModel, |
||
166 | SendEmailToContact $sendModel, |
||
167 | DeviceTracker $deviceTracker, |
||
168 | RedirectRepository $redirectRepository, |
||
169 | CacheStorageHelper $cacheStorageHelper, |
||
170 | ContactTracker $contactTracker, |
||
171 | DNC $doNotContact, |
||
172 | GeneratedColumnsProviderInterface $generatedColumnsProvider |
||
173 | ) { |
||
174 | $this->ipLookupHelper = $ipLookupHelper; |
||
175 | $this->themeHelper = $themeHelper; |
||
176 | $this->mailboxHelper = $mailboxHelper; |
||
177 | $this->mailHelper = $mailHelper; |
||
178 | $this->leadModel = $leadModel; |
||
179 | $this->companyModel = $companyModel; |
||
180 | $this->pageTrackableModel = $pageTrackableModel; |
||
181 | $this->userModel = $userModel; |
||
182 | $this->messageQueueModel = $messageQueueModel; |
||
183 | $this->sendModel = $sendModel; |
||
184 | $this->deviceTracker = $deviceTracker; |
||
185 | $this->redirectRepository = $redirectRepository; |
||
186 | $this->cacheStorageHelper = $cacheStorageHelper; |
||
187 | $this->contactTracker = $contactTracker; |
||
188 | $this->doNotContact = $doNotContact; |
||
189 | $this->generatedColumnsProvider = $generatedColumnsProvider; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * {@inheritdoc} |
||
194 | * |
||
195 | * @return \Mautic\EmailBundle\Entity\EmailRepository |
||
196 | */ |
||
197 | public function getRepository() |
||
198 | { |
||
199 | return $this->em->getRepository('MauticEmailBundle:Email'); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @return \Mautic\EmailBundle\Entity\StatRepository |
||
204 | */ |
||
205 | public function getStatRepository() |
||
206 | { |
||
207 | return $this->em->getRepository('MauticEmailBundle:Stat'); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @return \Mautic\EmailBundle\Entity\CopyRepository |
||
212 | */ |
||
213 | public function getCopyRepository() |
||
214 | { |
||
215 | return $this->em->getRepository('MauticEmailBundle:Copy'); |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * @return \Mautic\EmailBundle\Entity\StatDeviceRepository |
||
220 | */ |
||
221 | public function getStatDeviceRepository() |
||
222 | { |
||
223 | return $this->em->getRepository('MauticEmailBundle:StatDevice'); |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * {@inheritdoc} |
||
228 | */ |
||
229 | public function getPermissionBase() |
||
230 | { |
||
231 | return 'email:emails'; |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * {@inheritdoc} |
||
236 | * |
||
237 | * @param Email $entity |
||
238 | * @param $unlock |
||
239 | * |
||
240 | * @return mixed |
||
241 | */ |
||
242 | public function saveEntity($entity, $unlock = true) |
||
243 | { |
||
244 | $type = $entity->getEmailType(); |
||
245 | if (empty($type)) { |
||
246 | // Just in case JS failed |
||
247 | $entity->setEmailType('template'); |
||
248 | } |
||
249 | |||
250 | // Ensure that list emails are published |
||
251 | if ('list' == $entity->getEmailType()) { |
||
252 | // Ensure that this email has the same lists assigned as the translated parent if applicable |
||
253 | /** @var Email $translationParent */ |
||
254 | if ($translationParent = $entity->getTranslationParent()) { |
||
255 | $parentLists = $translationParent->getLists()->toArray(); |
||
256 | $entity->setLists($parentLists); |
||
257 | } |
||
258 | } else { |
||
259 | // Ensure that all lists are been removed in case of a clone |
||
260 | $entity->setLists([]); |
||
261 | } |
||
262 | |||
263 | if (!$this->updatingTranslationChildren) { |
||
264 | if (!$entity->isNew()) { |
||
265 | //increase the revision |
||
266 | $revision = $entity->getRevision(); |
||
267 | ++$revision; |
||
268 | $entity->setRevision($revision); |
||
269 | } |
||
270 | |||
271 | // Reset a/b test if applicable |
||
272 | if ($isVariant = $entity->isVariant()) { |
||
273 | $variantStartDate = new \DateTime(); |
||
274 | $resetVariants = $this->preVariantSaveEntity($entity, ['setVariantSentCount', 'setVariantReadCount'], $variantStartDate); |
||
275 | } |
||
276 | |||
277 | parent::saveEntity($entity, $unlock); |
||
278 | |||
279 | if ($isVariant) { |
||
280 | $emailIds = $entity->getRelatedEntityIds(); |
||
281 | $this->postVariantSaveEntity($entity, $resetVariants, $emailIds, $variantStartDate); |
||
282 | } |
||
283 | |||
284 | $this->postTranslationEntitySave($entity); |
||
285 | |||
286 | // Force translations for this entity to use the same segments |
||
287 | if ('list' == $entity->getEmailType() && $entity->hasTranslations()) { |
||
288 | $translations = $entity->getTranslationChildren()->toArray(); |
||
289 | $this->updatingTranslationChildren = true; |
||
290 | foreach ($translations as $translation) { |
||
291 | $this->saveEntity($translation); |
||
292 | } |
||
293 | $this->updatingTranslationChildren = false; |
||
294 | } |
||
295 | } else { |
||
296 | parent::saveEntity($entity, false); |
||
297 | } |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Save an array of entities. |
||
302 | * |
||
303 | * @param $entities |
||
304 | * @param $unlock |
||
305 | * |
||
306 | * @return array |
||
307 | */ |
||
308 | public function saveEntities($entities, $unlock = true) |
||
309 | { |
||
310 | //iterate over the results so the events are dispatched on each delete |
||
311 | $batchSize = 20; |
||
312 | $i = 0; |
||
313 | foreach ($entities as $entity) { |
||
314 | $isNew = ($entity->getId()) ? false : true; |
||
315 | |||
316 | //set some defaults |
||
317 | $this->setTimestamps($entity, $isNew, $unlock); |
||
318 | |||
319 | if ($dispatchEvent = $entity instanceof Email) { |
||
320 | $event = $this->dispatchEvent('pre_save', $entity, $isNew); |
||
321 | } |
||
322 | |||
323 | $this->getRepository()->saveEntity($entity, false); |
||
324 | |||
325 | if ($dispatchEvent) { |
||
326 | $this->dispatchEvent('post_save', $entity, $isNew, $event); |
||
327 | } |
||
328 | |||
329 | if (0 === ++$i % $batchSize) { |
||
330 | $this->em->flush(); |
||
331 | } |
||
332 | } |
||
333 | $this->em->flush(); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * @param Email $entity |
||
338 | */ |
||
339 | public function deleteEntity($entity) |
||
340 | { |
||
341 | if ($entity->isVariant() && $entity->getIsPublished()) { |
||
342 | $this->resetVariants($entity); |
||
343 | } |
||
344 | |||
345 | parent::deleteEntity($entity); |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * {@inheritdoc} |
||
350 | * |
||
351 | * @param $entity |
||
352 | * @param $formFactory |
||
353 | * @param null $action |
||
354 | * @param array $options |
||
355 | * |
||
356 | * @return mixed |
||
357 | * |
||
358 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException |
||
359 | */ |
||
360 | public function createForm($entity, $formFactory, $action = null, $options = []) |
||
361 | { |
||
362 | if (!$entity instanceof Email) { |
||
363 | throw new MethodNotAllowedHttpException(['Email']); |
||
364 | } |
||
365 | if (!empty($action)) { |
||
366 | $options['action'] = $action; |
||
367 | } |
||
368 | |||
369 | return $formFactory->create(EmailType::class, $entity, $options); |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Get a specific entity or generate a new one if id is empty. |
||
374 | * |
||
375 | * @param $id |
||
376 | * |
||
377 | * @return Email|null |
||
378 | */ |
||
379 | public function getEntity($id = null) |
||
380 | { |
||
381 | if (null === $id) { |
||
382 | $entity = new Email(); |
||
383 | $entity->setSessionId('new_'.hash('sha1', uniqid(mt_rand()))); |
||
384 | } else { |
||
385 | $entity = parent::getEntity($id); |
||
386 | if (null !== $entity) { |
||
387 | $entity->setSessionId($entity->getId()); |
||
388 | } |
||
389 | } |
||
390 | |||
391 | return $entity; |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Return a list of entities. |
||
396 | * |
||
397 | * @param array $args [start, limit, filter, orderBy, orderByDir] |
||
398 | * |
||
399 | * @return \Doctrine\ORM\Tools\Pagination\Paginator|array |
||
400 | */ |
||
401 | public function getEntities(array $args = []) |
||
402 | { |
||
403 | $entities = parent::getEntities($args); |
||
404 | |||
405 | foreach ($entities as $entity) { |
||
406 | $queued = $this->cacheStorageHelper->get(sprintf('%s|%s|%s', 'email', $entity->getId(), 'queued')); |
||
407 | $pending = $this->cacheStorageHelper->get(sprintf('%s|%s|%s', 'email', $entity->getId(), 'pending')); |
||
408 | |||
409 | if (false !== $queued) { |
||
410 | $entity->setQueuedCount($queued); |
||
411 | } |
||
412 | |||
413 | if (false !== $pending) { |
||
414 | $entity->setPendingCount($pending); |
||
415 | } |
||
416 | } |
||
417 | |||
418 | return $entities; |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * {@inheritdoc} |
||
423 | * |
||
424 | * @param $action |
||
425 | * @param $event |
||
426 | * @param $entity |
||
427 | * @param $isNew |
||
428 | * |
||
429 | * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException |
||
430 | */ |
||
431 | protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null) |
||
432 | { |
||
433 | if (!$entity instanceof Email) { |
||
434 | throw new MethodNotAllowedHttpException(['Email']); |
||
435 | } |
||
436 | |||
437 | switch ($action) { |
||
438 | case 'pre_save': |
||
439 | $name = EmailEvents::EMAIL_PRE_SAVE; |
||
440 | break; |
||
441 | case 'post_save': |
||
442 | $name = EmailEvents::EMAIL_POST_SAVE; |
||
443 | break; |
||
444 | case 'pre_delete': |
||
445 | $name = EmailEvents::EMAIL_PRE_DELETE; |
||
446 | break; |
||
447 | case 'post_delete': |
||
448 | $name = EmailEvents::EMAIL_POST_DELETE; |
||
449 | break; |
||
450 | default: |
||
451 | return null; |
||
452 | } |
||
453 | |||
454 | if ($this->dispatcher->hasListeners($name)) { |
||
455 | if (empty($event)) { |
||
456 | $event = new EmailEvent($entity, $isNew); |
||
457 | $event->setEntityManager($this->em); |
||
458 | } |
||
459 | |||
460 | $this->dispatcher->dispatch($name, $event); |
||
461 | |||
462 | return $event; |
||
463 | } else { |
||
464 | return null; |
||
465 | } |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * @param $stat |
||
470 | * @param $request |
||
471 | * @param bool $viaBrowser |
||
472 | * |
||
473 | * @throws \Exception |
||
474 | */ |
||
475 | public function hitEmail($stat, $request, $viaBrowser = false, $activeRequest = true) |
||
476 | { |
||
477 | if (!$stat instanceof Stat) { |
||
478 | $stat = $this->getEmailStatus($stat); |
||
479 | } |
||
480 | |||
481 | if (!$stat) { |
||
482 | return; |
||
483 | } |
||
484 | |||
485 | $email = $stat->getEmail(); |
||
486 | |||
487 | if ((int) $stat->isRead()) { |
||
488 | if ($viaBrowser && !$stat->getViewedInBrowser()) { |
||
489 | //opened via browser so note it |
||
490 | $stat->setViewedInBrowser($viaBrowser); |
||
491 | } |
||
492 | } |
||
493 | |||
494 | $readDateTime = new DateTimeHelper(); |
||
495 | $stat->setLastOpened($readDateTime->getDateTime()); |
||
496 | |||
497 | $lead = $stat->getLead(); |
||
498 | if (null !== $lead) { |
||
499 | // Set the lead as current lead |
||
500 | if ($activeRequest) { |
||
501 | $this->contactTracker->setTrackedContact($lead); |
||
502 | } else { |
||
503 | $this->contactTracker->setSystemContact($lead); |
||
504 | } |
||
505 | } |
||
506 | |||
507 | $firstTime = false; |
||
508 | if (!$stat->getIsRead()) { |
||
509 | $firstTime = true; |
||
510 | $stat->setIsRead(true); |
||
511 | $stat->setDateRead($readDateTime->getDateTime()); |
||
512 | |||
513 | // Only up counts if associated with both an email and lead |
||
514 | if ($email && $lead) { |
||
515 | try { |
||
516 | $this->getRepository()->upCount($email->getId(), 'read', 1, $email->isVariant()); |
||
517 | } catch (\Exception $exception) { |
||
518 | error_log($exception); |
||
519 | } |
||
520 | } |
||
521 | } |
||
522 | |||
523 | if ($viaBrowser) { |
||
524 | $stat->setViewedInBrowser($viaBrowser); |
||
525 | } |
||
526 | |||
527 | $stat->addOpenDetails( |
||
528 | [ |
||
529 | 'datetime' => $readDateTime->toUtcString(), |
||
530 | 'useragent' => $request->server->get('HTTP_USER_AGENT'), |
||
531 | 'inBrowser' => $viaBrowser, |
||
532 | ] |
||
533 | ); |
||
534 | |||
535 | //check for existing IP |
||
536 | $ipAddress = $this->ipLookupHelper->getIpAddress(); |
||
537 | $stat->setIpAddress($ipAddress); |
||
538 | |||
539 | if ($this->dispatcher->hasListeners(EmailEvents::EMAIL_ON_OPEN)) { |
||
540 | $event = new EmailOpenEvent($stat, $request, $firstTime); |
||
541 | $this->dispatcher->dispatch(EmailEvents::EMAIL_ON_OPEN, $event); |
||
542 | } |
||
543 | |||
544 | if ($email) { |
||
545 | $this->em->persist($email); |
||
546 | } |
||
547 | |||
548 | $this->em->persist($stat); |
||
549 | |||
550 | // Flush the email stat entity in different transactions than the device stat entity to avoid deadlocks. |
||
551 | $this->flushAndCatch(); |
||
552 | |||
553 | if ($lead) { |
||
554 | $trackedDevice = $this->deviceTracker->createDeviceFromUserAgent($lead, $request->server->get('HTTP_USER_AGENT')); |
||
555 | $emailOpenStat = new StatDevice(); |
||
556 | $emailOpenStat->setIpAddress($ipAddress); |
||
557 | $emailOpenStat->setDevice($trackedDevice); |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
558 | $emailOpenStat->setDateOpened($readDateTime->toUtcString()); |
||
559 | $emailOpenStat->setStat($stat); |
||
560 | |||
561 | $this->em->persist($emailOpenStat); |
||
562 | $this->flushAndCatch(); |
||
563 | } |
||
564 | } |
||
565 | |||
566 | /** |
||
567 | * Get array of page builder tokens from bundles subscribed PageEvents::PAGE_ON_BUILD. |
||
568 | * |
||
569 | * @param array|string $requestedComponents all | tokens | abTestWinnerCriteria |
||
570 | * @param string|null $tokenFilter |
||
571 | * |
||
572 | * @return array |
||
573 | */ |
||
574 | public function getBuilderComponents(Email $email = null, $requestedComponents = 'all', $tokenFilter = null, $withBC = true) |
||
575 | { |
||
576 | $event = new EmailBuilderEvent($this->translator, $email, $requestedComponents, $tokenFilter); |
||
577 | $this->dispatcher->dispatch(EmailEvents::EMAIL_ON_BUILD, $event); |
||
578 | |||
579 | return $this->getCommonBuilderComponents($requestedComponents, $event); |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * @param $limit |
||
584 | * @param array $options |
||
585 | * @param int|null $companyId |
||
586 | * @param int|null $campaignId |
||
587 | * @param int|null $segmentId |
||
588 | * |
||
589 | * @return array |
||
590 | */ |
||
591 | public function getSentEmailToContactData($limit, \DateTime $dateFrom, \DateTime $dateTo, $options = [], $companyId = null, $campaignId = null, $segmentId = null) |
||
592 | { |
||
593 | $createdByUserId = null; |
||
594 | $canViewOthers = empty($options['canViewOthers']) ? false : $options['canViewOthers']; |
||
595 | |||
596 | if (!$canViewOthers) { |
||
597 | $createdByUserId = $this->userHelper->getUser()->getId(); |
||
598 | } |
||
599 | |||
600 | $stats = $this->getStatRepository()->getSentEmailToContactData($limit, $dateFrom, $dateTo, $createdByUserId, $companyId, $campaignId, $segmentId); |
||
601 | $data = []; |
||
602 | |||
603 | foreach ($stats as $stat) { |
||
604 | $statId = $stat['id']; |
||
605 | |||
606 | if (empty($stat['segment_id']) && !empty($stat['campaign_id'])) { |
||
607 | // Let's fetch the segment based on current campaign/segment membership |
||
608 | $segmentMembership = $this->em->getRepository('MauticCampaignBundle:Campaign') |
||
609 | ->getContactSingleSegmentByCampaign($stat['lead_id'], $stat['campaign_id']); |
||
610 | |||
611 | if ($segmentMembership) { |
||
612 | $stat['segment_id'] = $segmentMembership['id']; |
||
613 | $stat['segment_name'] = $segmentMembership['name']; |
||
614 | } |
||
615 | } |
||
616 | |||
617 | $item = [ |
||
618 | 'contact_id' => $stat['lead_id'], |
||
619 | 'contact_email' => $stat['email_address'], |
||
620 | 'open' => $stat['is_read'], |
||
621 | 'click' => (null !== $stat['link_hits']) ? $stat['link_hits'] : 0, |
||
622 | 'links_clicked' => [], |
||
623 | 'email_id' => (string) $stat['email_id'], |
||
624 | 'email_name' => (string) $stat['email_name'], |
||
625 | 'segment_id' => (string) $stat['segment_id'], |
||
626 | 'segment_name' => (string) $stat['segment_name'], |
||
627 | 'company_id' => (string) $stat['company_id'], |
||
628 | 'company_name' => (string) $stat['company_name'], |
||
629 | 'campaign_id' => (string) $stat['campaign_id'], |
||
630 | 'campaign_name' => (string) $stat['campaign_name'], |
||
631 | 'date_sent' => $stat['date_sent'], |
||
632 | 'date_read' => $stat['date_read'], |
||
633 | ]; |
||
634 | |||
635 | if ($item['click'] && $item['email_id'] && $item['contact_id']) { |
||
636 | $item['links_clicked'] = $this->getStatRepository()->getUniqueClickedLinksPerContactAndEmail($item['contact_id'], $item['email_id']); |
||
637 | } |
||
638 | |||
639 | $data[$statId] = $item; |
||
640 | } |
||
641 | |||
642 | return $data; |
||
643 | } |
||
644 | |||
645 | /** |
||
646 | * @param int $limit |
||
647 | * @param array $options |
||
648 | * @param int|null $companyId |
||
649 | * @param int|null $campaignId |
||
650 | * @param int|null $segmentId |
||
651 | * |
||
652 | * @return array |
||
653 | */ |
||
654 | public function getMostHitEmailRedirects($limit, \DateTime $dateFrom, \DateTime $dateTo, $options = [], $companyId = null, $campaignId = null, $segmentId = null) |
||
655 | { |
||
656 | $createdByUserId = null; |
||
657 | $canViewOthers = empty($options['canViewOthers']) ? false : $options['canViewOthers']; |
||
658 | |||
659 | if (!$canViewOthers) { |
||
660 | $createdByUserId = $this->userHelper->getUser()->getId(); |
||
661 | } |
||
662 | |||
663 | $redirects = $this->redirectRepository->getMostHitEmailRedirects($limit, $dateFrom, $dateTo, $createdByUserId, $companyId, $campaignId, $segmentId); |
||
664 | $data = []; |
||
665 | foreach ($redirects as $redirect) { |
||
666 | $data[] = [ |
||
667 | 'url' => (string) $redirect['url'], |
||
668 | 'unique_hits' => (string) $redirect['unique_hits'], |
||
669 | 'hits' => (string) $redirect['hits'], |
||
670 | 'email_id' => (string) $redirect['email_id'], |
||
671 | 'email_name' => (string) $redirect['email_name'], |
||
672 | ]; |
||
673 | } |
||
674 | |||
675 | return $data; |
||
676 | } |
||
677 | |||
678 | /** |
||
679 | * @param $idHash |
||
680 | * |
||
681 | * @return Stat |
||
682 | */ |
||
683 | public function getEmailStatus($idHash) |
||
684 | { |
||
685 | return $this->getStatRepository()->getEmailStatus($idHash); |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Search for an email stat by email and lead IDs. |
||
690 | * |
||
691 | * @param $emailId |
||
692 | * @param $leadId |
||
693 | * |
||
694 | * @return array |
||
695 | */ |
||
696 | public function getEmailStati($emailId, $leadId) |
||
697 | { |
||
698 | return $this->getStatRepository()->findBy( |
||
699 | [ |
||
700 | 'email' => (int) $emailId, |
||
701 | 'lead' => (int) $leadId, |
||
702 | ], |
||
703 | ['dateSent' => 'DESC'] |
||
704 | ); |
||
705 | } |
||
706 | |||
707 | /** |
||
708 | * Get a stats for email by list. |
||
709 | * |
||
710 | * @param $email |
||
711 | * @param bool $includeVariants |
||
712 | * |
||
713 | * @return array |
||
714 | */ |
||
715 | public function getEmailListStats($email, $includeVariants = false, \DateTime $dateFrom = null, \DateTime $dateTo = null) |
||
716 | { |
||
717 | if (!$email instanceof Email) { |
||
718 | $email = $this->getEntity($email); |
||
719 | } |
||
720 | |||
721 | $emailIds = ($includeVariants && ($email->isVariant() || $email->isTranslation())) ? $email->getRelatedEntityIds() : [$email->getId()]; |
||
722 | |||
723 | $lists = $email->getLists(); |
||
724 | $listCount = count($lists); |
||
725 | $chart = new BarChart( |
||
726 | [ |
||
727 | $this->translator->trans('mautic.email.sent'), |
||
728 | $this->translator->trans('mautic.email.read'), |
||
729 | $this->translator->trans('mautic.email.failed'), |
||
730 | $this->translator->trans('mautic.email.clicked'), |
||
731 | $this->translator->trans('mautic.email.unsubscribed'), |
||
732 | $this->translator->trans('mautic.email.bounced'), |
||
733 | ] |
||
734 | ); |
||
735 | |||
736 | if ($listCount) { |
||
737 | /** @var \Mautic\EmailBundle\Entity\StatRepository $statRepo */ |
||
738 | $statRepo = $this->em->getRepository('MauticEmailBundle:Stat'); |
||
739 | |||
740 | /** @var \Mautic\LeadBundle\Entity\DoNotContactRepository $dncRepo */ |
||
741 | $dncRepo = $this->em->getRepository('MauticLeadBundle:DoNotContact'); |
||
742 | |||
743 | /** @var \Mautic\PageBundle\Entity\TrackableRepository $trackableRepo */ |
||
744 | $trackableRepo = $this->em->getRepository('MauticPageBundle:Trackable'); |
||
745 | |||
746 | $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); |
||
747 | $key = ($listCount > 1) ? 1 : 0; |
||
748 | |||
749 | $sentCounts = $statRepo->getSentCount($emailIds, $lists->getKeys(), $query); |
||
750 | $readCounts = $statRepo->getReadCount($emailIds, $lists->getKeys(), $query); |
||
751 | $failedCounts = $statRepo->getFailedCount($emailIds, $lists->getKeys(), $query); |
||
752 | $clickCounts = $trackableRepo->getCount('email', $emailIds, $lists->getKeys(), $query, false, 'DISTINCT ph.lead_id'); |
||
753 | $unsubscribedCounts = $dncRepo->getCount('email', $emailIds, DoNotContact::UNSUBSCRIBED, $lists->getKeys(), $query); |
||
754 | $bouncedCounts = $dncRepo->getCount('email', $emailIds, DoNotContact::BOUNCED, $lists->getKeys(), $query); |
||
755 | |||
756 | foreach ($lists as $l) { |
||
757 | $sentCount = isset($sentCounts[$l->getId()]) ? $sentCounts[$l->getId()] : 0; |
||
758 | $readCount = isset($readCounts[$l->getId()]) ? $readCounts[$l->getId()] : 0; |
||
759 | $failedCount = isset($failedCounts[$l->getId()]) ? $failedCounts[$l->getId()] : 0; |
||
760 | $clickCount = isset($clickCounts[$l->getId()]) ? $clickCounts[$l->getId()] : 0; |
||
761 | $unsubscribedCount = isset($unsubscribedCounts[$l->getId()]) ? $unsubscribedCounts[$l->getId()] : 0; |
||
762 | $bouncedCount = isset($bouncedCounts[$l->getId()]) ? $bouncedCounts[$l->getId()] : 0; |
||
763 | |||
764 | $chart->setDataset( |
||
765 | $l->getName(), |
||
766 | [ |
||
767 | $sentCount, |
||
768 | $readCount, |
||
769 | $failedCount, |
||
770 | $clickCount, |
||
771 | $unsubscribedCount, |
||
772 | $bouncedCount, |
||
773 | ], |
||
774 | $key |
||
775 | ); |
||
776 | |||
777 | ++$key; |
||
778 | } |
||
779 | |||
780 | $combined = [ |
||
781 | $statRepo->getSentCount($emailIds, $lists->getKeys(), $query, true), |
||
782 | $statRepo->getReadCount($emailIds, $lists->getKeys(), $query, true), |
||
783 | $statRepo->getFailedCount($emailIds, $lists->getKeys(), $query, true), |
||
784 | $trackableRepo->getCount('email', $emailIds, $lists->getKeys(), $query, true, 'DISTINCT ph.lead_id'), |
||
785 | $dncRepo->getCount('email', $emailIds, DoNotContact::UNSUBSCRIBED, $lists->getKeys(), $query, true), |
||
786 | $dncRepo->getCount('email', $emailIds, DoNotContact::BOUNCED, $lists->getKeys(), $query, true), |
||
787 | ]; |
||
788 | |||
789 | if ($listCount > 1) { |
||
790 | $chart->setDataset( |
||
791 | $this->translator->trans('mautic.email.lists.combined'), |
||
792 | $combined, |
||
793 | 0 |
||
794 | ); |
||
795 | } |
||
796 | } |
||
797 | |||
798 | return $chart->render(); |
||
799 | } |
||
800 | |||
801 | /** |
||
802 | * Get a stats for email by list. |
||
803 | * |
||
804 | * @param Email|int $email |
||
805 | * @param bool $includeVariants |
||
806 | * |
||
807 | * @return array |
||
808 | */ |
||
809 | public function getEmailDeviceStats($email, $includeVariants = false, $dateFrom = null, $dateTo = null) |
||
810 | { |
||
811 | if (!$email instanceof Email) { |
||
812 | $email = $this->getEntity($email); |
||
813 | } |
||
814 | |||
815 | $emailIds = ($includeVariants) ? $email->getRelatedEntityIds() : [$email->getId()]; |
||
816 | $templateEmail = 'template' === $email->getEmailType(); |
||
817 | $results = $this->getStatDeviceRepository()->getDeviceStats($emailIds, $dateFrom, $dateTo); |
||
818 | |||
819 | // Organize by list_id (if a segment email) and/or device |
||
820 | $stats = []; |
||
821 | $devices = []; |
||
822 | foreach ($results as $result) { |
||
823 | if (empty($result['device'])) { |
||
824 | $result['device'] = $this->translator->trans('mautic.core.unknown'); |
||
825 | } else { |
||
826 | $result['device'] = mb_substr($result['device'], 0, 12); |
||
827 | } |
||
828 | $devices[$result['device']] = $result['device']; |
||
829 | |||
830 | if ($templateEmail) { |
||
831 | // List doesn't matter |
||
832 | $stats[$result['device']] = $result['count']; |
||
833 | } elseif (null !== $result['list_id']) { |
||
834 | if (!isset($stats[$result['list_id']])) { |
||
835 | $stats[$result['list_id']] = []; |
||
836 | } |
||
837 | |||
838 | if (!isset($stats[$result['list_id']][$result['device']])) { |
||
839 | $stats[$result['list_id']][$result['device']] = (int) $result['count']; |
||
840 | } else { |
||
841 | $stats[$result['list_id']][$result['device']] += (int) $result['count']; |
||
842 | } |
||
843 | } |
||
844 | } |
||
845 | |||
846 | $listCount = 0; |
||
847 | if (!$templateEmail) { |
||
848 | $lists = $email->getLists(); |
||
849 | $listNames = []; |
||
850 | foreach ($lists as $l) { |
||
851 | $listNames[$l->getId()] = $l->getName(); |
||
852 | } |
||
853 | $listCount = count($listNames); |
||
854 | } |
||
855 | |||
856 | natcasesort($devices); |
||
857 | $chart = new BarChart(array_values($devices)); |
||
858 | |||
859 | if ($templateEmail) { |
||
860 | // Populate the data |
||
861 | $chart->setDataset( |
||
862 | null, |
||
863 | array_values($stats), |
||
864 | 0 |
||
865 | ); |
||
866 | } else { |
||
867 | $combined = []; |
||
868 | $key = ($listCount > 1) ? 1 : 0; |
||
869 | foreach ($listNames as $id => $name) { |
||
870 | // Fill in missing devices |
||
871 | $listStats = []; |
||
872 | foreach ($devices as $device) { |
||
873 | $listStat = (!isset($stats[$id][$device])) ? 0 : $stats[$id][$device]; |
||
874 | $listStats[] = $listStat; |
||
875 | |||
876 | if (!isset($combined[$device])) { |
||
877 | $combined[$device] = 0; |
||
878 | } |
||
879 | |||
880 | $combined[$device] += $listStat; |
||
881 | } |
||
882 | |||
883 | // Populate the data |
||
884 | $chart->setDataset( |
||
885 | $name, |
||
886 | $listStats, |
||
887 | $key |
||
888 | ); |
||
889 | |||
890 | ++$key; |
||
891 | } |
||
892 | |||
893 | if ($listCount > 1) { |
||
894 | $chart->setDataset( |
||
895 | $this->translator->trans('mautic.email.lists.combined'), |
||
896 | array_values($combined), |
||
897 | 0 |
||
898 | ); |
||
899 | } |
||
900 | } |
||
901 | |||
902 | return $chart->render(); |
||
903 | } |
||
904 | |||
905 | /** |
||
906 | * @param $email |
||
907 | * @param bool $includeVariants |
||
908 | * @param $unit |
||
909 | * |
||
910 | * @return array |
||
911 | */ |
||
912 | public function getEmailGeneralStats($email, $includeVariants, $unit, \DateTime $dateFrom, \DateTime $dateTo) |
||
913 | { |
||
914 | if (!$email instanceof Email) { |
||
915 | $email = $this->getEntity($email); |
||
916 | } |
||
917 | |||
918 | $filter = [ |
||
919 | 'email_id' => ($includeVariants) ? $email->getRelatedEntityIds() : [$email->getId()], |
||
920 | 'flag' => 'all', |
||
921 | ]; |
||
922 | |||
923 | return $this->getEmailsLineChartData($unit, $dateFrom, $dateTo, null, $filter); |
||
924 | } |
||
925 | |||
926 | /** |
||
927 | * Get an array of tracked links. |
||
928 | * |
||
929 | * @param $emailId |
||
930 | * |
||
931 | * @return array |
||
932 | */ |
||
933 | public function getEmailClickStats($emailId) |
||
934 | { |
||
935 | return $this->pageTrackableModel->getTrackableList('email', $emailId); |
||
936 | } |
||
937 | |||
938 | /** |
||
939 | * Get the number of leads this email will be sent to. |
||
940 | * |
||
941 | * @param mixed $listId Leads for a specific lead list |
||
942 | * @param bool $countOnly If true, return count otherwise array of leads |
||
943 | * @param int $limit Max number of leads to retrieve |
||
944 | * @param bool $includeVariants If false, emails sent to a variant will not be included |
||
945 | * @param int $minContactId Filter by min contact ID |
||
946 | * @param int $maxContactId Filter by max contact ID |
||
947 | * @param bool $countWithMaxMin Add min_id and max_id info to the count result |
||
948 | * @param bool $storeToCache Whether to store the result to the cache |
||
949 | * |
||
950 | * @return int|array |
||
951 | */ |
||
952 | public function getPendingLeads( |
||
953 | Email $email, |
||
954 | $listId = null, |
||
955 | $countOnly = false, |
||
956 | $limit = null, |
||
957 | $includeVariants = true, |
||
958 | $minContactId = null, |
||
959 | $maxContactId = null, |
||
960 | $countWithMaxMin = false, |
||
961 | $storeToCache = true |
||
962 | ) { |
||
963 | $variantIds = ($includeVariants) ? $email->getRelatedEntityIds() : null; |
||
964 | $total = $this->getRepository()->getEmailPendingLeads( |
||
965 | $email->getId(), |
||
966 | $variantIds, |
||
967 | $listId, |
||
968 | $countOnly, |
||
969 | $limit, |
||
970 | $minContactId, |
||
971 | $maxContactId, |
||
972 | $countWithMaxMin |
||
973 | ); |
||
974 | |||
975 | if ($storeToCache) { |
||
976 | if ($countOnly && $countWithMaxMin) { |
||
977 | $toStore = $total['count']; |
||
978 | } elseif ($countOnly) { |
||
979 | $toStore = $total; |
||
980 | } else { |
||
981 | $toStore = count($total); |
||
982 | } |
||
983 | |||
984 | $this->cacheStorageHelper->set(sprintf('%s|%s|%s', 'email', $email->getId(), 'pending'), $toStore); |
||
985 | } |
||
986 | |||
987 | return $total; |
||
988 | } |
||
989 | |||
990 | /** |
||
991 | * @param bool $includeVariants |
||
992 | * |
||
993 | * @return array|int |
||
994 | */ |
||
995 | public function getQueuedCounts(Email $email, $includeVariants = true) |
||
996 | { |
||
997 | $ids = ($includeVariants) ? $email->getRelatedEntityIds() : null; |
||
998 | if (!in_array($email->getId(), $ids)) { |
||
999 | $ids[] = $email->getId(); |
||
1000 | } |
||
1001 | |||
1002 | $queued = (int) $this->messageQueueModel->getQueuedChannelCount('email', $ids); |
||
1003 | $this->cacheStorageHelper->set(sprintf('%s|%s|%s', 'email', $email->getId(), 'queued'), $queued); |
||
1004 | |||
1005 | return $queued; |
||
1006 | } |
||
1007 | |||
1008 | /** |
||
1009 | * Send an email to lead lists. |
||
1010 | * |
||
1011 | * @param array $lists |
||
1012 | * @param int $limit |
||
1013 | * @param bool $batch True to process and batch all pending leads |
||
1014 | * @param OutputInterface $output |
||
1015 | * @param int $minContactId |
||
1016 | * @param int $maxContactId |
||
1017 | * |
||
1018 | * @return array array(int $sentCount, int $failedCount, array $failedRecipientsByList) |
||
1019 | */ |
||
1020 | public function sendEmailToLists( |
||
1021 | Email $email, |
||
1022 | $lists = null, |
||
1023 | $limit = null, |
||
1024 | $batch = false, |
||
1025 | OutputInterface $output = null, |
||
1026 | $minContactId = null, |
||
1027 | $maxContactId = null |
||
1028 | ) { |
||
1029 | //get the leads |
||
1030 | if (empty($lists)) { |
||
1031 | $lists = $email->getLists(); |
||
1032 | } |
||
1033 | |||
1034 | // Safety check |
||
1035 | if ('list' !== $email->getEmailType()) { |
||
1036 | return [0, 0, []]; |
||
1037 | } |
||
1038 | |||
1039 | // Doesn't make sense to send unpublished emails. Probably a user error. |
||
1040 | // @todo throw an exception in Mautic 3 here. |
||
1041 | if (!$email->isPublished()) { |
||
1042 | return [0, 0, []]; |
||
1043 | } |
||
1044 | |||
1045 | $options = [ |
||
1046 | 'source' => ['email', $email->getId()], |
||
1047 | 'allowResends' => false, |
||
1048 | 'customHeaders' => [ |
||
1049 | 'Precedence' => 'Bulk', |
||
1050 | 'X-EMAIL-ID' => $email->getId(), |
||
1051 | ], |
||
1052 | ]; |
||
1053 | |||
1054 | $failedRecipientsByList = []; |
||
1055 | $sentCount = 0; |
||
1056 | $failedCount = 0; |
||
1057 | |||
1058 | $progress = false; |
||
1059 | if ($batch && $output) { |
||
1060 | $progressCounter = 0; |
||
1061 | $totalLeadCount = $this->getPendingLeads($email, null, true, null, true, $minContactId, $maxContactId, false, false); |
||
1062 | if (!$totalLeadCount) { |
||
1063 | return [0, 0, []]; |
||
1064 | } |
||
1065 | |||
1066 | // Broadcast send through CLI |
||
1067 | $output->writeln("\n<info>".$email->getName().'</info>'); |
||
1068 | $progress = new ProgressBar($output, $totalLeadCount); |
||
1069 | } |
||
1070 | |||
1071 | foreach ($lists as $list) { |
||
1072 | if (!$batch && null !== $limit && $limit <= 0) { |
||
1073 | // Hit the max for this batch |
||
1074 | break; |
||
1075 | } |
||
1076 | |||
1077 | $options['listId'] = $list->getId(); |
||
1078 | $leads = $this->getPendingLeads($email, $list->getId(), false, $limit, true, $minContactId, $maxContactId, false, false); |
||
1079 | $leadCount = count($leads); |
||
1080 | |||
1081 | while ($leadCount) { |
||
1082 | $sentCount += $leadCount; |
||
1083 | |||
1084 | if (!$batch && null != $limit) { |
||
1085 | // Only retrieve the difference between what has already been sent and the limit |
||
1086 | $limit -= $leadCount; |
||
1087 | } |
||
1088 | |||
1089 | $listErrors = $this->sendEmail($email, $leads, $options); |
||
1090 | |||
1091 | if (!empty($listErrors)) { |
||
1092 | $listFailedCount = count($listErrors); |
||
1093 | |||
1094 | $sentCount -= $listFailedCount; |
||
1095 | $failedCount += $listFailedCount; |
||
1096 | |||
1097 | $failedRecipientsByList[$options['listId']] = $listErrors; |
||
1098 | } |
||
1099 | |||
1100 | if ($batch) { |
||
1101 | if ($progress) { |
||
1102 | $progressCounter += $leadCount; |
||
1103 | $progress->setProgress($progressCounter); |
||
1104 | } |
||
1105 | |||
1106 | // Get the next batch of leads |
||
1107 | $leads = $this->getPendingLeads($email, $list->getId(), false, $limit, true, $minContactId, $maxContactId, false, false); |
||
1108 | $leadCount = count($leads); |
||
1109 | } else { |
||
1110 | $leadCount = 0; |
||
1111 | } |
||
1112 | } |
||
1113 | } |
||
1114 | |||
1115 | if ($progress) { |
||
1116 | $progress->finish(); |
||
1117 | } |
||
1118 | |||
1119 | return [$sentCount, $failedCount, $failedRecipientsByList]; |
||
1120 | } |
||
1121 | |||
1122 | /** |
||
1123 | * Gets template, stats, weights, etc for an email in preparation to be sent. |
||
1124 | * |
||
1125 | * @param bool $includeVariants |
||
1126 | * |
||
1127 | * @return array |
||
1128 | */ |
||
1129 | public function &getEmailSettings(Email $email, $includeVariants = true) |
||
1130 | { |
||
1131 | if (empty($this->emailSettings[$email->getId()])) { |
||
1132 | //used to house slots so they don't have to be fetched over and over for same template |
||
1133 | // BC for Mautic v1 templates |
||
1134 | $slots = []; |
||
1135 | if ($template = $email->getTemplate()) { |
||
1136 | $slots[$template] = $this->themeHelper->getTheme($template)->getSlots('email'); |
||
1137 | } |
||
1138 | |||
1139 | //store the settings of all the variants in order to properly disperse the emails |
||
1140 | //set the parent's settings |
||
1141 | $emailSettings = [ |
||
1142 | $email->getId() => [ |
||
1143 | 'template' => $email->getTemplate(), |
||
1144 | 'slots' => $slots, |
||
1145 | 'sentCount' => $email->getSentCount(), |
||
1146 | 'variantCount' => $email->getVariantSentCount(), |
||
1147 | 'isVariant' => null !== $email->getVariantStartDate(), |
||
1148 | 'entity' => $email, |
||
1149 | 'translations' => $email->getTranslations(true), |
||
1150 | 'languages' => ['default' => $email->getId()], |
||
1151 | ], |
||
1152 | ]; |
||
1153 | |||
1154 | if ($emailSettings[$email->getId()]['translations']) { |
||
1155 | // Add in the sent counts for translations of this email |
||
1156 | /** @var Email $translation */ |
||
1157 | foreach ($emailSettings[$email->getId()]['translations'] as $translation) { |
||
1158 | if ($translation->isPublished()) { |
||
1159 | $emailSettings[$email->getId()]['sentCount'] += $translation->getSentCount(); |
||
1160 | $emailSettings[$email->getId()]['variantCount'] += $translation->getVariantSentCount(); |
||
1161 | |||
1162 | // Prevent empty key due to misconfiguration - pretty much ignored |
||
1163 | if (!$language = $translation->getLanguage()) { |
||
1164 | $language = 'unknown'; |
||
1165 | } |
||
1166 | $core = $this->getTranslationLocaleCore($language); |
||
1167 | if (!isset($emailSettings[$email->getId()]['languages'][$core])) { |
||
1168 | $emailSettings[$email->getId()]['languages'][$core] = []; |
||
1169 | } |
||
1170 | $emailSettings[$email->getId()]['languages'][$core][$language] = $translation->getId(); |
||
1171 | } |
||
1172 | } |
||
1173 | } |
||
1174 | |||
1175 | if ($includeVariants && $email->isVariant()) { |
||
1176 | //get a list of variants for A/B testing |
||
1177 | $childrenVariant = $email->getVariantChildren(); |
||
1178 | |||
1179 | if (count($childrenVariant)) { |
||
1180 | $variantWeight = 0; |
||
1181 | $totalSent = $emailSettings[$email->getId()]['variantCount']; |
||
1182 | |||
1183 | foreach ($childrenVariant as $child) { |
||
1184 | if ($child->isPublished()) { |
||
1185 | $useSlots = []; |
||
1186 | if ($template = $child->getTemplate()) { |
||
1187 | if (isset($slots[$template])) { |
||
1188 | $useSlots = $slots[$template]; |
||
1189 | } else { |
||
1190 | $slots[$template] = $this->themeHelper->getTheme($template)->getSlots('email'); |
||
1191 | $useSlots = $slots[$template]; |
||
1192 | } |
||
1193 | } |
||
1194 | $variantSettings = $child->getVariantSettings(); |
||
1195 | $emailSettings[$child->getId()] = [ |
||
1196 | 'template' => $child->getTemplate(), |
||
1197 | 'slots' => $useSlots, |
||
1198 | 'sentCount' => $child->getSentCount(), |
||
1199 | 'variantCount' => $child->getVariantSentCount(), |
||
1200 | 'isVariant' => null !== $email->getVariantStartDate(), |
||
1201 | 'weight' => ($variantSettings['weight'] / 100), |
||
1202 | 'entity' => $child, |
||
1203 | 'translations' => $child->getTranslations(true), |
||
1204 | 'languages' => ['default' => $child->getId()], |
||
1205 | ]; |
||
1206 | |||
1207 | $variantWeight += $variantSettings['weight']; |
||
1208 | |||
1209 | if ($emailSettings[$child->getId()]['translations']) { |
||
1210 | // Add in the sent counts for translations of this email |
||
1211 | /** @var Email $translation */ |
||
1212 | foreach ($emailSettings[$child->getId()]['translations'] as $translation) { |
||
1213 | if ($translation->isPublished()) { |
||
1214 | $emailSettings[$child->getId()]['sentCount'] += $translation->getSentCount(); |
||
1215 | $emailSettings[$child->getId()]['variantCount'] += $translation->getVariantSentCount(); |
||
1216 | |||
1217 | // Prevent empty key due to misconfiguration - pretty much ignored |
||
1218 | if (!$language = $translation->getLanguage()) { |
||
1219 | $language = 'unknown'; |
||
1220 | } |
||
1221 | $core = $this->getTranslationLocaleCore($language); |
||
1222 | if (!isset($emailSettings[$child->getId()]['languages'][$core])) { |
||
1223 | $emailSettings[$child->getId()]['languages'][$core] = []; |
||
1224 | } |
||
1225 | $emailSettings[$child->getId()]['languages'][$core][$language] = $translation->getId(); |
||
1226 | } |
||
1227 | } |
||
1228 | } |
||
1229 | |||
1230 | $totalSent += $emailSettings[$child->getId()]['variantCount']; |
||
1231 | } |
||
1232 | } |
||
1233 | |||
1234 | //set parent weight |
||
1235 | $emailSettings[$email->getId()]['weight'] = ((100 - $variantWeight) / 100); |
||
1236 | } else { |
||
1237 | $emailSettings[$email->getId()]['weight'] = 1; |
||
1238 | } |
||
1239 | } |
||
1240 | |||
1241 | $this->emailSettings[$email->getId()] = $emailSettings; |
||
1242 | } |
||
1243 | |||
1244 | if ($includeVariants && $email->isVariant()) { |
||
1245 | //now find what percentage of current leads should receive the variants |
||
1246 | if (!isset($totalSent)) { |
||
1247 | $totalSent = 0; |
||
1248 | foreach ($this->emailSettings[$email->getId()] as $details) { |
||
1249 | $totalSent += $details['variantCount']; |
||
1250 | } |
||
1251 | } |
||
1252 | |||
1253 | foreach ($this->emailSettings[$email->getId()] as &$details) { |
||
1254 | // Determine the deficit for email ordering |
||
1255 | if ($totalSent) { |
||
1256 | $details['weight_deficit'] = $details['weight'] - ($details['variantCount'] / $totalSent); |
||
1257 | $details['send_weight'] = ($details['weight'] - ($details['variantCount'] / $totalSent)) + $details['weight']; |
||
1258 | } else { |
||
1259 | $details['weight_deficit'] = $details['weight']; |
||
1260 | $details['send_weight'] = $details['weight']; |
||
1261 | } |
||
1262 | } |
||
1263 | |||
1264 | // Reorder according to send_weight so that campaigns which currently send one at a time alternate |
||
1265 | uasort($this->emailSettings[$email->getId()], function ($a, $b) { |
||
1266 | if ($a['weight_deficit'] === $b['weight_deficit']) { |
||
1267 | if ($a['variantCount'] === $b['variantCount']) { |
||
1268 | return 0; |
||
1269 | } |
||
1270 | |||
1271 | // if weight is the same - sort by least number sent |
||
1272 | return ($a['variantCount'] < $b['variantCount']) ? -1 : 1; |
||
1273 | } |
||
1274 | |||
1275 | // sort by the one with the greatest deficit first |
||
1276 | return ($a['weight_deficit'] > $b['weight_deficit']) ? -1 : 1; |
||
1277 | }); |
||
1278 | } |
||
1279 | |||
1280 | return $this->emailSettings[$email->getId()]; |
||
1281 | } |
||
1282 | |||
1283 | /** |
||
1284 | * Send an email to lead(s). |
||
1285 | * |
||
1286 | * @param $email |
||
1287 | * @param $leads |
||
1288 | * @param $options = array() |
||
1289 | * array source array('model', 'id') |
||
1290 | * array emailSettings |
||
1291 | * int listId |
||
1292 | * bool allowResends If false, exact emails (by id) already sent to the lead will not be resent |
||
1293 | * bool ignoreDNC If true, emails listed in the do not contact table will still get the email |
||
1294 | * array assetAttachments Array of optional Asset IDs to attach |
||
1295 | * |
||
1296 | * @return mixed |
||
1297 | * |
||
1298 | * @throws \Doctrine\ORM\ORMException |
||
1299 | */ |
||
1300 | public function sendEmail(Email $email, $leads, $options = []) |
||
1301 | { |
||
1302 | $listId = ArrayHelper::getValue('listId', $options); |
||
1303 | $ignoreDNC = ArrayHelper::getValue('ignoreDNC', $options, false); |
||
1304 | $tokens = ArrayHelper::getValue('tokens', $options, []); |
||
1305 | $assetAttachments = ArrayHelper::getValue('assetAttachments', $options, []); |
||
1306 | $customHeaders = ArrayHelper::getValue('customHeaders', $options, []); |
||
1307 | $emailType = ArrayHelper::getValue('email_type', $options, ''); |
||
1308 | $isMarketing = (in_array($emailType, ['marketing']) || !empty($listId)); |
||
1309 | $emailAttempts = ArrayHelper::getValue('email_attempts', $options, 3); |
||
1310 | $emailPriority = ArrayHelper::getValue('email_priority', $options, MessageQueue::PRIORITY_NORMAL); |
||
1311 | $messageQueue = ArrayHelper::getValue('resend_message_queue', $options); |
||
1312 | $returnErrorMessages = ArrayHelper::getValue('return_errors', $options, false); |
||
1313 | $channel = ArrayHelper::getValue('channel', $options); |
||
1314 | $dncAsError = ArrayHelper::getValue('dnc_as_error', $options, false); |
||
1315 | $errors = []; |
||
1316 | |||
1317 | if (empty($channel)) { |
||
1318 | $channel = (isset($options['source'])) ? $options['source'] : []; |
||
1319 | } |
||
1320 | |||
1321 | if (!$email->getId()) { |
||
1322 | return false; |
||
1323 | } |
||
1324 | |||
1325 | // Ensure $sendTo is indexed by lead ID |
||
1326 | $leadIds = []; |
||
1327 | $singleEmail = false; |
||
1328 | if (isset($leads['id'])) { |
||
1329 | $singleEmail = $leads['id']; |
||
1330 | $leadIds[$leads['id']] = $leads['id']; |
||
1331 | $leads = [$leads['id'] => $leads]; |
||
1332 | $sendTo = $leads; |
||
1333 | } else { |
||
1334 | $sendTo = []; |
||
1335 | foreach ($leads as $lead) { |
||
1336 | $sendTo[$lead['id']] = $lead; |
||
1337 | $leadIds[$lead['id']] = $lead['id']; |
||
1338 | } |
||
1339 | } |
||
1340 | |||
1341 | /** @var \Mautic\EmailBundle\Entity\EmailRepository $emailRepo */ |
||
1342 | $emailRepo = $this->getRepository(); |
||
1343 | |||
1344 | //get email settings such as templates, weights, etc |
||
1345 | $emailSettings = &$this->getEmailSettings($email); |
||
1346 | |||
1347 | if (!$ignoreDNC) { |
||
1348 | $dnc = $emailRepo->getDoNotEmailList($leadIds); |
||
1349 | |||
1350 | if (!empty($dnc)) { |
||
1351 | foreach ($dnc as $removeMeId => $removeMeEmail) { |
||
1352 | if ($dncAsError) { |
||
1353 | $errors[$removeMeId] = $this->translator->trans('mautic.email.dnc'); |
||
1354 | } |
||
1355 | unset($sendTo[$removeMeId]); |
||
1356 | unset($leadIds[$removeMeId]); |
||
1357 | } |
||
1358 | } |
||
1359 | } |
||
1360 | |||
1361 | // Process frequency rules for email |
||
1362 | if ($isMarketing && count($sendTo)) { |
||
1363 | $campaignEventId = (is_array($channel) && !empty($channel) && 'campaign.event' === $channel[0] && !empty($channel[1])) ? $channel[1] |
||
1364 | : null; |
||
1365 | $this->messageQueueModel->processFrequencyRules( |
||
1366 | $sendTo, |
||
1367 | 'email', |
||
1368 | $email->getId(), |
||
1369 | $campaignEventId, |
||
1370 | $emailAttempts, |
||
1371 | $emailPriority, |
||
1372 | $messageQueue |
||
1373 | ); |
||
1374 | } |
||
1375 | |||
1376 | //get a count of leads |
||
1377 | $count = count($sendTo); |
||
1378 | |||
1379 | //no one to send to so bail or if marketing email from a campaign has been put in a queue |
||
1380 | if (empty($count)) { |
||
1381 | if ($returnErrorMessages) { |
||
1382 | return $singleEmail && isset($errors[$singleEmail]) ? $errors[$singleEmail] : $errors; |
||
1383 | } |
||
1384 | |||
1385 | return $singleEmail ? true : $errors; |
||
1386 | } |
||
1387 | |||
1388 | // Hydrate contacts with company profile fields |
||
1389 | $this->getContactCompanies($sendTo); |
||
1390 | |||
1391 | foreach ($emailSettings as $eid => $details) { |
||
1392 | if (isset($details['send_weight'])) { |
||
1393 | $emailSettings[$eid]['limit'] = ceil($count * $details['send_weight']); |
||
1394 | } else { |
||
1395 | $emailSettings[$eid]['limit'] = $count; |
||
1396 | } |
||
1397 | } |
||
1398 | |||
1399 | // Randomize the contacts for statistic purposes |
||
1400 | shuffle($sendTo); |
||
1401 | |||
1402 | // Organize the contacts according to the variant and translation they are to receive |
||
1403 | $groupedContactsByEmail = []; |
||
1404 | $offset = 0; |
||
1405 | foreach ($emailSettings as $eid => $details) { |
||
1406 | if (empty($details['limit'])) { |
||
1407 | continue; |
||
1408 | } |
||
1409 | $groupedContactsByEmail[$eid] = []; |
||
1410 | if ($details['limit']) { |
||
1411 | // Take a chunk of contacts based on variant weights |
||
1412 | if ($batchContacts = array_slice($sendTo, $offset, $details['limit'])) { |
||
1413 | $offset += $details['limit']; |
||
1414 | |||
1415 | // Group contacts by preferred locale |
||
1416 | foreach ($batchContacts as $key => $contact) { |
||
1417 | if (!empty($contact['preferred_locale'])) { |
||
1418 | $locale = $contact['preferred_locale']; |
||
1419 | $localeCore = $this->getTranslationLocaleCore($locale); |
||
1420 | |||
1421 | if (isset($details['languages'][$localeCore])) { |
||
1422 | if (isset($details['languages'][$localeCore][$locale])) { |
||
1423 | // Exact match |
||
1424 | $translatedId = $details['languages'][$localeCore][$locale]; |
||
1425 | $groupedContactsByEmail[$eid][$translatedId][] = $contact; |
||
1426 | } else { |
||
1427 | // Grab the closest match |
||
1428 | $bestMatch = array_keys($details['languages'][$localeCore])[0]; |
||
1429 | $translatedId = $details['languages'][$localeCore][$bestMatch]; |
||
1430 | $groupedContactsByEmail[$eid][$translatedId][] = $contact; |
||
1431 | } |
||
1432 | |||
1433 | unset($batchContacts[$key]); |
||
1434 | } |
||
1435 | } |
||
1436 | } |
||
1437 | |||
1438 | // If there are any contacts left over, assign them to the default |
||
1439 | if (count($batchContacts)) { |
||
1440 | $translatedId = $details['languages']['default']; |
||
1441 | $groupedContactsByEmail[$eid][$translatedId] = $batchContacts; |
||
1442 | } |
||
1443 | } |
||
1444 | } |
||
1445 | } |
||
1446 | |||
1447 | foreach ($groupedContactsByEmail as $parentId => $translatedEmails) { |
||
1448 | $useSettings = $emailSettings[$parentId]; |
||
1449 | foreach ($translatedEmails as $translatedId => $contacts) { |
||
1450 | $emailEntity = ($translatedId === $parentId) ? $useSettings['entity'] : $useSettings['translations'][$translatedId]; |
||
1451 | |||
1452 | $this->sendModel->setEmail($emailEntity, $channel, $customHeaders, $assetAttachments) |
||
1453 | ->setListId($listId); |
||
1454 | |||
1455 | foreach ($contacts as $contact) { |
||
1456 | try { |
||
1457 | $this->sendModel->setContact($contact, $tokens) |
||
1458 | ->send(); |
||
1459 | |||
1460 | // Update $emailSetting so campaign a/b tests are handled correctly |
||
1461 | ++$emailSettings[$parentId]['sentCount']; |
||
1462 | |||
1463 | if (!empty($emailSettings[$parentId]['isVariant'])) { |
||
1464 | ++$emailSettings[$parentId]['variantCount']; |
||
1465 | } |
||
1466 | } catch (FailedToSendToContactException $exception) { |
||
1467 | // move along to the next contact |
||
1468 | } |
||
1469 | } |
||
1470 | } |
||
1471 | } |
||
1472 | |||
1473 | // Flush the queue and store pending email stats |
||
1474 | $this->sendModel->finalFlush(); |
||
1475 | |||
1476 | // Get the errors to return |
||
1477 | |||
1478 | // Don't use array_merge or it will reset contact ID based keys |
||
1479 | $errorMessages = $errors + $this->sendModel->getErrors(); |
||
1480 | $failedContacts = $this->sendModel->getFailedContacts(); |
||
1481 | |||
1482 | // Get sent counts to update email stats |
||
1483 | $sentCounts = $this->sendModel->getSentCounts(); |
||
1484 | |||
1485 | // Reset the model for the next send |
||
1486 | $this->sendModel->reset(); |
||
1487 | |||
1488 | // Update sent counts |
||
1489 | foreach ($sentCounts as $emailId => $count) { |
||
1490 | // Retry a few times in case of deadlock errors |
||
1491 | $strikes = 3; |
||
1492 | while ($strikes >= 0) { |
||
1493 | try { |
||
1494 | $this->getRepository()->upCount($emailId, 'sent', $count, $emailSettings[$emailId]['isVariant']); |
||
1495 | break; |
||
1496 | } catch (\Exception $exception) { |
||
1497 | error_log($exception); |
||
1498 | } |
||
1499 | --$strikes; |
||
1500 | } |
||
1501 | } |
||
1502 | |||
1503 | unset($emailSettings, $options, $sendTo); |
||
1504 | |||
1505 | $success = empty($failedContacts); |
||
1506 | if (!$success && $returnErrorMessages) { |
||
1507 | return $singleEmail ? $errorMessages[$singleEmail] : $errorMessages; |
||
1508 | } |
||
1509 | |||
1510 | return $singleEmail ? $success : $failedContacts; |
||
1511 | } |
||
1512 | |||
1513 | /** |
||
1514 | * Send an email to lead(s). |
||
1515 | * |
||
1516 | * @param array|int $users |
||
1517 | * @param array $lead |
||
1518 | * @param bool $saveStat |
||
1519 | * |
||
1520 | * @return mixed |
||
1521 | * |
||
1522 | * @throws \Doctrine\ORM\ORMException |
||
1523 | */ |
||
1524 | public function sendEmailToUser( |
||
1525 | Email $email, |
||
1526 | $users, |
||
1527 | array $lead = null, |
||
1528 | array $tokens = [], |
||
1529 | array $assetAttachments = [], |
||
1530 | $saveStat = false, |
||
1531 | array $to = [], |
||
1532 | array $cc = [], |
||
1533 | array $bcc = [] |
||
1534 | ) { |
||
1535 | if (!$emailId = $email->getId()) { |
||
1536 | return false; |
||
1537 | } |
||
1538 | |||
1539 | // In case only user ID was provided |
||
1540 | if (!is_array($users)) { |
||
1541 | $users = [['id' => $users]]; |
||
1542 | } |
||
1543 | |||
1544 | // Get email settings |
||
1545 | $emailSettings = &$this->getEmailSettings($email, false); |
||
1546 | |||
1547 | // No one to send to so bail |
||
1548 | if (empty($users) && empty($to)) { |
||
1549 | return false; |
||
1550 | } |
||
1551 | |||
1552 | $mailer = $this->mailHelper->getMailer(); |
||
1553 | if (!isset($lead['companies'])) { |
||
1554 | $lead['companies'] = $this->companyModel->getRepository()->getCompaniesByLeadId($lead['id']); |
||
1555 | } |
||
1556 | $mailer->setLead($lead, true); |
||
1557 | $mailer->setTokens($tokens); |
||
1558 | $mailer->setEmail($email, false, $emailSettings[$emailId]['slots'], $assetAttachments, (!$saveStat)); |
||
1559 | $mailer->setCc($cc); |
||
1560 | $mailer->setBcc($bcc); |
||
1561 | |||
1562 | $errors = []; |
||
1563 | |||
1564 | $firstMail = true; |
||
1565 | foreach ($to as $toAddress) { |
||
1566 | $idHash = uniqid(); |
||
1567 | $mailer->setIdHash($idHash, $saveStat); |
||
1568 | |||
1569 | if (!$mailer->addTo($toAddress)) { |
||
1570 | $errors[] = "{$toAddress}: ".$this->translator->trans('mautic.email.bounce.reason.bad_email'); |
||
1571 | continue; |
||
1572 | } |
||
1573 | |||
1574 | if (!$mailer->queue(true)) { |
||
1575 | $errorArray = $mailer->getErrors(); |
||
1576 | unset($errorArray['failures']); |
||
1577 | $errors[] = "{$toAddress}: ".implode('; ', $errorArray); |
||
1578 | } |
||
1579 | |||
1580 | if ($saveStat) { |
||
1581 | $saveEntities[] = $mailer->createEmailStat(false, $toAddress); |
||
1582 | } |
||
1583 | |||
1584 | // If this is the first message, flush the queue. This process clears the cc and bcc. |
||
1585 | if (true === $firstMail) { |
||
1586 | try { |
||
1587 | $this->flushQueue($mailer); |
||
1588 | } catch (EmailCouldNotBeSentException $e) { |
||
1589 | $errors[] = $e->getMessage(); |
||
1590 | } |
||
1591 | $firstMail = false; |
||
1592 | } |
||
1593 | } |
||
1594 | |||
1595 | foreach ($users as $user) { |
||
1596 | $idHash = uniqid(); |
||
1597 | $mailer->setIdHash($idHash, $saveStat); |
||
1598 | |||
1599 | if (!is_array($user)) { |
||
1600 | $id = $user; |
||
1601 | $user = ['id' => $id]; |
||
1602 | } else { |
||
1603 | $id = $user['id']; |
||
1604 | } |
||
1605 | |||
1606 | if (!isset($user['email'])) { |
||
1607 | $userEntity = $this->userModel->getEntity($id); |
||
1608 | |||
1609 | if (null === $userEntity) { |
||
1610 | continue; |
||
1611 | } |
||
1612 | |||
1613 | $user['email'] = $userEntity->getEmail(); |
||
1614 | $user['firstname'] = $userEntity->getFirstName(); |
||
1615 | $user['lastname'] = $userEntity->getLastName(); |
||
1616 | } |
||
1617 | |||
1618 | if (!$mailer->setTo($user['email'], $user['firstname'].' '.$user['lastname'])) { |
||
1619 | $errors[] = "{$user['email']}: ".$this->translator->trans('mautic.email.bounce.reason.bad_email'); |
||
1620 | continue; |
||
1621 | } |
||
1622 | |||
1623 | if (!$mailer->queue(true)) { |
||
1624 | $errorArray = $mailer->getErrors(); |
||
1625 | unset($errorArray['failures']); |
||
1626 | $errors[] = "{$user['email']}: ".implode('; ', $errorArray); |
||
1627 | } |
||
1628 | |||
1629 | if ($saveStat) { |
||
1630 | $saveEntities[] = $mailer->createEmailStat(false, $user['email']); |
||
1631 | } |
||
1632 | |||
1633 | // If this is the first message, flush the queue. This process clears the cc and bcc. |
||
1634 | if (true === $firstMail) { |
||
1635 | try { |
||
1636 | $this->flushQueue($mailer); |
||
1637 | } catch (EmailCouldNotBeSentException $e) { |
||
1638 | $errors[] = $e->getMessage(); |
||
1639 | } |
||
1640 | $firstMail = false; |
||
1641 | } |
||
1642 | } |
||
1643 | |||
1644 | try { |
||
1645 | $this->flushQueue($mailer); |
||
1646 | } catch (EmailCouldNotBeSentException $e) { |
||
1647 | $errors[] = $e->getMessage(); |
||
1648 | } |
||
1649 | |||
1650 | if (isset($saveEntities)) { |
||
1651 | $this->getStatRepository()->saveEntities($saveEntities); |
||
1652 | } |
||
1653 | |||
1654 | //save some memory |
||
1655 | unset($mailer); |
||
1656 | |||
1657 | return $errors; |
||
1658 | } |
||
1659 | |||
1660 | /** |
||
1661 | * @throws EmailCouldNotBeSentException |
||
1662 | */ |
||
1663 | private function flushQueue(MailHelper $mailer): void |
||
1664 | { |
||
1665 | if (!$mailer->flushQueue()) { |
||
1666 | $errorArray = $mailer->getErrors(); |
||
1667 | unset($errorArray['failures']); |
||
1668 | |||
1669 | throw new EmailCouldNotBeSentException(implode('; ', $errorArray)); |
||
1670 | } |
||
1671 | } |
||
1672 | |||
1673 | /** |
||
1674 | * Dispatches EmailSendEvent so you could get tokens form it or tokenized content. |
||
1675 | * |
||
1676 | * @param string $idHash |
||
1677 | * |
||
1678 | * @return EmailSendEvent |
||
1679 | */ |
||
1680 | public function dispatchEmailSendEvent(Email $email, array $leadFields = [], $idHash = null, array $tokens = []) |
||
1681 | { |
||
1682 | $event = new EmailSendEvent( |
||
1683 | null, |
||
1684 | [ |
||
1685 | 'content' => $email->getCustomHtml(), |
||
1686 | 'email' => $email, |
||
1687 | 'idHash' => $idHash, |
||
1688 | 'tokens' => $tokens, |
||
1689 | 'internalSend' => true, |
||
1690 | 'lead' => $leadFields, |
||
1691 | ] |
||
1692 | ); |
||
1693 | |||
1694 | $this->dispatcher->dispatch(EmailEvents::EMAIL_ON_DISPLAY, $event); |
||
1695 | |||
1696 | return $event; |
||
1697 | } |
||
1698 | |||
1699 | /** |
||
1700 | * @param $comments |
||
1701 | * @param int $reason |
||
1702 | * @param bool $flush |
||
1703 | * |
||
1704 | * @return bool|DoNotContact |
||
1705 | */ |
||
1706 | public function setDoNotContact(Stat $stat, $comments, $reason = DoNotContact::BOUNCED, $flush = true) |
||
1707 | { |
||
1708 | $lead = $stat->getLead(); |
||
1709 | |||
1710 | if ($lead instanceof Lead) { |
||
1711 | $email = $stat->getEmail(); |
||
1712 | $channel = ($email) ? ['email' => $email->getId()] : 'email'; |
||
1713 | |||
1714 | return $this->doNotContact->addDncForContact($lead->getId(), $channel, $reason, $comments, $flush); |
||
1715 | } |
||
1716 | |||
1717 | return false; |
||
1718 | } |
||
1719 | |||
1720 | /** |
||
1721 | * Remove a Lead's EMAIL DNC entry. |
||
1722 | * |
||
1723 | * @param string $email |
||
1724 | */ |
||
1725 | public function removeDoNotContact($email) |
||
1726 | { |
||
1727 | /** @var \Mautic\LeadBundle\Entity\LeadRepository $leadRepo */ |
||
1728 | $leadRepo = $this->em->getRepository('MauticLeadBundle:Lead'); |
||
1729 | $leadId = (array) $leadRepo->getLeadByEmail($email, true); |
||
1730 | |||
1731 | /** @var \Mautic\LeadBundle\Entity\Lead[] $leads */ |
||
1732 | $leads = []; |
||
1733 | |||
1734 | foreach ($leadId as $lead) { |
||
1735 | $leads[] = $leadRepo->getEntity($lead['id']); |
||
1736 | } |
||
1737 | |||
1738 | foreach ($leads as $lead) { |
||
1739 | $this->doNotContact->removeDncForContact($lead->getId(), 'email'); |
||
1740 | } |
||
1741 | } |
||
1742 | |||
1743 | /** |
||
1744 | * @param $email |
||
1745 | * @param int $reason |
||
1746 | * @param string $comments |
||
1747 | * @param bool $flush |
||
1748 | * @param null $leadId |
||
1749 | * |
||
1750 | * @return array |
||
1751 | */ |
||
1752 | public function setEmailDoNotContact($email, $reason = DoNotContact::BOUNCED, $comments = '', $flush = true, $leadId = null) |
||
1753 | { |
||
1754 | /** @var \Mautic\LeadBundle\Entity\LeadRepository $leadRepo */ |
||
1755 | $leadRepo = $this->em->getRepository('MauticLeadBundle:Lead'); |
||
1756 | |||
1757 | if (null === $leadId) { |
||
1758 | $leadId = (array) $leadRepo->getLeadByEmail($email, true); |
||
1759 | } elseif (!is_array($leadId)) { |
||
1760 | $leadId = [$leadId]; |
||
1761 | } |
||
1762 | |||
1763 | $dnc = []; |
||
1764 | foreach ($leadId as $lead) { |
||
1765 | $dnc[] = $this->doNotContact->addDncForContact( |
||
1766 | $this->em->getReference('MauticLeadBundle:Lead', $lead), |
||
1767 | 'email', |
||
1768 | $reason, |
||
1769 | $comments, |
||
1770 | $flush |
||
1771 | ); |
||
1772 | } |
||
1773 | |||
1774 | return $dnc; |
||
1775 | } |
||
1776 | |||
1777 | /** |
||
1778 | * Get the settings for a monitored mailbox or false if not enabled. |
||
1779 | * |
||
1780 | * @param $bundleKey |
||
1781 | * @param $folderKey |
||
1782 | * |
||
1783 | * @return bool|array |
||
1784 | */ |
||
1785 | public function getMonitoredMailbox($bundleKey, $folderKey) |
||
1786 | { |
||
1787 | if ($this->mailboxHelper->isConfigured($bundleKey, $folderKey)) { |
||
1788 | return $this->mailboxHelper->getMailboxSettings(); |
||
1789 | } |
||
1790 | |||
1791 | return false; |
||
1792 | } |
||
1793 | |||
1794 | /** |
||
1795 | * Joins the email table and limits created_by to currently logged in user. |
||
1796 | */ |
||
1797 | public function limitQueryToCreator(QueryBuilder &$q) |
||
1798 | { |
||
1799 | $q->join('t', MAUTIC_TABLE_PREFIX.'emails', 'e', 'e.id = t.email_id') |
||
1800 | ->andWhere('e.created_by = :userId') |
||
1801 | ->setParameter('userId', $this->userHelper->getUser()->getId()); |
||
1802 | } |
||
1803 | |||
1804 | /** |
||
1805 | * Get line chart data of emails sent and read. |
||
1806 | * |
||
1807 | * @param $reason |
||
1808 | * @param $canViewOthers |
||
1809 | * @param int|null $companyId |
||
1810 | * @param int|null $campaignId |
||
1811 | * @param int|null $segmentId |
||
1812 | * |
||
1813 | * @return array |
||
1814 | */ |
||
1815 | public function getEmailsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, array $filter = [], $canViewOthers = true) |
||
1816 | { |
||
1817 | $datasets = ArrayHelper::pickValue('dataset', $filter, []); |
||
1818 | $flag = ArrayHelper::pickValue('flag', $filter); |
||
1819 | $companyId = ArrayHelper::pickValue('companyId', $filter); |
||
1820 | $campaignId = ArrayHelper::pickValue('campaignId', $filter); |
||
1821 | $segmentId = ArrayHelper::pickValue('segmentId', $filter); |
||
1822 | $chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat); |
||
1823 | $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo, $unit); |
||
1824 | |||
1825 | $query->setGeneratedColumnProvider($this->generatedColumnsProvider); |
||
1826 | |||
1827 | if ('sent_and_opened_and_failed' == $flag || 'all' == $flag || 'sent_and_opened' == $flag || !$flag || in_array('sent', $datasets)) { |
||
1828 | $q = $query->prepareTimeDataQuery('email_stats', 'date_sent', $filter); |
||
1829 | if (!$canViewOthers) { |
||
1830 | $this->limitQueryToCreator($q); |
||
1831 | } |
||
1832 | $this->addCompanyFilter($q, $companyId); |
||
1833 | $this->addCampaignFilter($q, $campaignId); |
||
1834 | $this->addSegmentFilter($q, $segmentId); |
||
1835 | $data = $query->loadAndBuildTimeData($q); |
||
1836 | $chart->setDataset($this->translator->trans('mautic.email.sent.emails'), $data); |
||
1837 | } |
||
1838 | |||
1839 | if ('sent_and_opened_and_failed' == $flag || 'all' == $flag || 'sent_and_opened' == $flag || 'opened' == $flag || in_array('opened', $datasets)) { |
||
1840 | $q = $query->prepareTimeDataQuery('email_stats', 'date_read', $filter); |
||
1841 | if (!$canViewOthers) { |
||
1842 | $this->limitQueryToCreator($q); |
||
1843 | } |
||
1844 | $this->addCompanyFilter($q, $companyId); |
||
1845 | $this->addCampaignFilter($q, $campaignId); |
||
1846 | $this->addSegmentFilter($q, $segmentId); |
||
1847 | $data = $query->loadAndBuildTimeData($q); |
||
1848 | $chart->setDataset($this->translator->trans('mautic.email.read.emails'), $data); |
||
1849 | } |
||
1850 | |||
1851 | if ('sent_and_opened_and_failed' == $flag || 'all' == $flag || 'failed' == $flag || in_array('failed', $datasets)) { |
||
1852 | $q = $query->prepareTimeDataQuery('email_stats', 'date_sent', $filter); |
||
1853 | if (!$canViewOthers) { |
||
1854 | $this->limitQueryToCreator($q); |
||
1855 | } |
||
1856 | $q->andWhere($q->expr()->eq('t.is_failed', ':true')) |
||
1857 | ->setParameter('true', true, 'boolean'); |
||
1858 | $this->addCompanyFilter($q, $companyId); |
||
1859 | $this->addCampaignFilter($q, $campaignId); |
||
1860 | $this->addSegmentFilter($q, $segmentId); |
||
1861 | $data = $query->loadAndBuildTimeData($q); |
||
1862 | $chart->setDataset($this->translator->trans('mautic.email.failed.emails'), $data); |
||
1863 | } |
||
1864 | |||
1865 | if ('all' == $flag || 'clicked' == $flag || in_array('clicked', $datasets)) { |
||
1866 | $q = $query->prepareTimeDataQuery('page_hits', 'date_hit', []); |
||
1867 | |||
1868 | if (null !== $segmentId) { |
||
1869 | $q->innerJoin('t', '(SELECT DISTINCT email_id, lead_id FROM '.MAUTIC_TABLE_PREFIX.'email_stats WHERE list_id = :segmentId)', 'es', 't.source_id = es.email_id'); |
||
1870 | $q->setParameter('segmentId', $segmentId); |
||
1871 | } |
||
1872 | |||
1873 | $q->andWhere('t.source = :source'); |
||
1874 | $q->setParameter('source', 'email'); |
||
1875 | |||
1876 | if (isset($filter['email_id'])) { |
||
1877 | if (is_array($filter['email_id'])) { |
||
1878 | $q->andWhere('t.source_id IN (:email_ids)'); |
||
1879 | $q->setParameter('email_ids', $filter['email_id'], \Doctrine\DBAL\Connection::PARAM_INT_ARRAY); |
||
1880 | } else { |
||
1881 | $q->andWhere('t.source_id = :email_id'); |
||
1882 | $q->setParameter('email_id', $filter['email_id']); |
||
1883 | } |
||
1884 | } |
||
1885 | |||
1886 | if (!$canViewOthers) { |
||
1887 | $this->limitQueryToCreator($q); |
||
1888 | } |
||
1889 | $this->addCompanyFilter($q, $companyId); |
||
1890 | $this->addCampaignFilterForEmailSource($q, $campaignId); |
||
1891 | $this->addSegmentFilter($q, $segmentId, 'es'); |
||
1892 | $data = $query->loadAndBuildTimeData($q); |
||
1893 | |||
1894 | $chart->setDataset($this->translator->trans('mautic.email.clicked'), $data); |
||
1895 | } |
||
1896 | |||
1897 | if ('all' == $flag || 'unsubscribed' == $flag || in_array('unsubscribed', $datasets)) { |
||
1898 | $data = $this->getDncLineChartDataset($query, $filter, DoNotContact::UNSUBSCRIBED, $canViewOthers, $companyId, $campaignId, $segmentId); |
||
1899 | $chart->setDataset($this->translator->trans('mautic.email.unsubscribed'), $data); |
||
1900 | } |
||
1901 | |||
1902 | if ('all' == $flag || 'bounced' == $flag || in_array('bounced', $datasets)) { |
||
1903 | $data = $this->getDncLineChartDataset($query, $filter, DoNotContact::BOUNCED, $canViewOthers, $companyId, $campaignId, $segmentId); |
||
1904 | $chart->setDataset($this->translator->trans('mautic.email.bounced'), $data); |
||
1905 | } |
||
1906 | |||
1907 | return $chart->render(); |
||
1908 | } |
||
1909 | |||
1910 | /** |
||
1911 | * Modifies the line chart query for the DNC. |
||
1912 | * |
||
1913 | * @param $reason |
||
1914 | * @param $canViewOthers |
||
1915 | * @param int|null $companyId |
||
1916 | * @param int|null $campaignId |
||
1917 | * @param int|null $segmentId |
||
1918 | * |
||
1919 | * @return array |
||
1920 | */ |
||
1921 | public function getDncLineChartDataset(ChartQuery &$query, array $filter, $reason, $canViewOthers, $companyId = null, $campaignId = null, $segmentId = null) |
||
1922 | { |
||
1923 | $dncFilter = isset($filter['email_id']) ? ['channel_id' => $filter['email_id']] : []; |
||
1924 | $q = $query->prepareTimeDataQuery('lead_donotcontact', 'date_added', $dncFilter); |
||
1925 | $q->andWhere('t.channel = :channel') |
||
1926 | ->setParameter('channel', 'email') |
||
1927 | ->andWhere($q->expr()->eq('t.reason', ':reason')) |
||
1928 | ->setParameter('reason', $reason); |
||
1929 | |||
1930 | $q->leftJoin('t', MAUTIC_TABLE_PREFIX.'email_stats', 'es', 't.channel_id = es.email_id AND t.channel = "email" AND t.lead_id = es.lead_id'); |
||
1931 | |||
1932 | if (!$canViewOthers) { |
||
1933 | $this->limitQueryToCreator($q); |
||
1934 | } |
||
1935 | $this->addCompanyFilter($q, $companyId); |
||
1936 | $this->addCampaignFilter($q, $campaignId, 'es'); |
||
1937 | $this->addSegmentFilter($q, $segmentId, 'es'); |
||
1938 | |||
1939 | return $data = $query->loadAndBuildTimeData($q); |
||
1940 | } |
||
1941 | |||
1942 | /** |
||
1943 | * @param int|null $companyId |
||
1944 | * @param string $fromAlias |
||
1945 | */ |
||
1946 | private function addCompanyFilter(QueryBuilder $q, $companyId = null, $fromAlias = 't') |
||
1947 | { |
||
1948 | if (!$companyId) { |
||
1949 | return; |
||
1950 | } |
||
1951 | |||
1952 | $sb = $this->em->getConnection()->createQueryBuilder(); |
||
1953 | |||
1954 | $sb->select('null') |
||
1955 | ->from(MAUTIC_TABLE_PREFIX.'companies_leads', 'cl') |
||
1956 | ->where( |
||
1957 | $sb->expr()->andX( |
||
1958 | $sb->expr()->eq('cl.company_id', ':companyId'), |
||
1959 | $sb->expr()->eq('cl.lead_id', $fromAlias.'.lead_id') |
||
1960 | ) |
||
1961 | ); |
||
1962 | |||
1963 | $q->andWhere( |
||
1964 | sprintf('EXISTS (%s)', $sb->getSql()) |
||
1965 | )->setParameter('companyId', $companyId); |
||
1966 | } |
||
1967 | |||
1968 | /** |
||
1969 | * @param int|null $campaignId |
||
1970 | * @param string $fromAlias |
||
1971 | */ |
||
1972 | private function addCampaignFilter(QueryBuilder $q, $campaignId = null, $fromAlias = 't') |
||
1973 | { |
||
1974 | if ($campaignId) { |
||
1975 | $q->innerJoin($fromAlias, '(SELECT DISTINCT event_id, lead_id FROM '.MAUTIC_TABLE_PREFIX.'campaign_lead_event_log WHERE campaign_id = :campaignId)', 'clel', $fromAlias.'.source_id = clel.event_id AND '.$fromAlias.'.source = "campaign.event" AND '.$fromAlias.'.lead_id = clel.lead_id') |
||
1976 | ->setParameter('campaignId', $campaignId); |
||
1977 | } |
||
1978 | } |
||
1979 | |||
1980 | /** |
||
1981 | * @param int|null $campaignId |
||
1982 | * @param string $fromAlias |
||
1983 | */ |
||
1984 | private function addCampaignFilterForEmailSource(QueryBuilder $q, $campaignId = null, $fromAlias = 't') |
||
1985 | { |
||
1986 | if ($campaignId) { |
||
1987 | $q->innerJoin($fromAlias, '(SELECT DISTINCT channel_id, lead_id FROM '.MAUTIC_TABLE_PREFIX.'campaign_lead_event_log WHERE campaign_id = :campaignId AND channel = "email")', 'clel', $fromAlias.'.source_id = clel.channel_id AND '.$fromAlias.'.source = "email" AND '.$fromAlias.'.lead_id = clel.lead_id') |
||
1988 | ->setParameter('campaignId', $campaignId); |
||
1989 | } |
||
1990 | } |
||
1991 | |||
1992 | /** |
||
1993 | * @param int|null $segmentId |
||
1994 | * @param string $fromAlias |
||
1995 | */ |
||
1996 | private function addSegmentFilter(QueryBuilder $q, $segmentId = null, $fromAlias = 't') |
||
1997 | { |
||
1998 | if ($segmentId) { |
||
1999 | $sb = $this->em->getConnection()->createQueryBuilder(); |
||
2000 | |||
2001 | $sb->select('null') |
||
2002 | ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'lll') |
||
2003 | ->where( |
||
2004 | $sb->expr()->andX( |
||
2005 | $sb->expr()->eq('lll.leadlist_id', ':segmentId'), |
||
2006 | $sb->expr()->eq('lll.lead_id', $fromAlias.'.lead_id'), |
||
2007 | $sb->expr()->eq('lll.manually_removed', 0) |
||
2008 | ) |
||
2009 | ); |
||
2010 | |||
2011 | $q->andWhere( |
||
2012 | sprintf('EXISTS (%s)', $sb->getSql()) |
||
2013 | )->setParameter('segmentId', $segmentId); |
||
2014 | } |
||
2015 | } |
||
2016 | |||
2017 | /** |
||
2018 | * Get pie chart data of ignored vs opened emails. |
||
2019 | * |
||
2020 | * @param string $dateFrom |
||
2021 | * @param string $dateTo |
||
2022 | * @param array $filters |
||
2023 | * @param bool $canViewOthers |
||
2024 | * |
||
2025 | * @return array |
||
2026 | */ |
||
2027 | public function getIgnoredVsReadPieChartData($dateFrom, $dateTo, $filters = [], $canViewOthers = true) |
||
2028 | { |
||
2029 | $chart = new PieChart(); |
||
2030 | $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); |
||
2031 | |||
2032 | $readFilters = $filters; |
||
2033 | $readFilters['is_read'] = true; |
||
2034 | $failedFilters = $filters; |
||
2035 | $failedFilters['is_failed'] = true; |
||
2036 | |||
2037 | $sentQ = $query->getCountQuery('email_stats', 'id', 'date_sent', $filters); |
||
2038 | $readQ = $query->getCountQuery('email_stats', 'id', 'date_sent', $readFilters); |
||
2039 | $failedQ = $query->getCountQuery('email_stats', 'id', 'date_sent', $failedFilters); |
||
2040 | |||
2041 | if (!$canViewOthers) { |
||
2042 | $this->limitQueryToCreator($sentQ); |
||
2043 | $this->limitQueryToCreator($readQ); |
||
2044 | $this->limitQueryToCreator($failedQ); |
||
2045 | } |
||
2046 | |||
2047 | $sent = $query->fetchCount($sentQ); |
||
2048 | $read = $query->fetchCount($readQ); |
||
2049 | $failed = $query->fetchCount($failedQ); |
||
2050 | |||
2051 | $chart->setDataset($this->translator->trans('mautic.email.graph.pie.ignored.read.failed.ignored'), ($sent - $read - $failed)); |
||
2052 | $chart->setDataset($this->translator->trans('mautic.email.graph.pie.ignored.read.failed.read'), $read); |
||
2053 | $chart->setDataset($this->translator->trans('mautic.email.graph.pie.ignored.read.failed.failed'), $failed); |
||
2054 | |||
2055 | return $chart->render(); |
||
2056 | } |
||
2057 | |||
2058 | /** |
||
2059 | * Get pie chart data of ignored vs opened emails. |
||
2060 | * |
||
2061 | * @param $dateFrom |
||
2062 | * @param $dateTo |
||
2063 | * |
||
2064 | * @return array |
||
2065 | */ |
||
2066 | public function getDeviceGranularityPieChartData($dateFrom, $dateTo) |
||
2067 | { |
||
2068 | $chart = new PieChart(); |
||
2069 | |||
2070 | $deviceStats = $this->getStatDeviceRepository()->getDeviceStats( |
||
2071 | null, |
||
2072 | $dateFrom, |
||
2073 | $dateTo |
||
2074 | ); |
||
2075 | |||
2076 | if (empty($deviceStats)) { |
||
2077 | $deviceStats[] = [ |
||
2078 | 'count' => 0, |
||
2079 | 'device' => $this->translator->trans('mautic.report.report.noresults'), |
||
2080 | 'list_id' => 0, |
||
2081 | ]; |
||
2082 | } |
||
2083 | |||
2084 | foreach ($deviceStats as $device) { |
||
2085 | $chart->setDataset( |
||
2086 | ($device['device']) ? $device['device'] : $this->translator->trans('mautic.core.unknown'), |
||
2087 | $device['count'] |
||
2088 | ); |
||
2089 | } |
||
2090 | |||
2091 | return $chart->render(); |
||
2092 | } |
||
2093 | |||
2094 | /** |
||
2095 | * Get a list of emails in a date range, grouped by a stat date count. |
||
2096 | * |
||
2097 | * @param int $limit |
||
2098 | * @param \DateTime $dateFrom |
||
2099 | * @param \DateTime $dateTo |
||
2100 | * @param array $filters |
||
2101 | * @param array $options |
||
2102 | * |
||
2103 | * @return array |
||
2104 | */ |
||
2105 | public function getEmailStatList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = []) |
||
2106 | { |
||
2107 | $canViewOthers = empty($options['canViewOthers']) ? false : $options['canViewOthers']; |
||
2108 | $q = $this->em->getConnection()->createQueryBuilder(); |
||
2109 | $q->select('COUNT(DISTINCT t.id) AS count, e.id, e.name') |
||
2110 | ->from(MAUTIC_TABLE_PREFIX.'email_stats', 't') |
||
2111 | ->join('t', MAUTIC_TABLE_PREFIX.'emails', 'e', 'e.id = t.email_id') |
||
2112 | ->orderBy('count', 'DESC') |
||
2113 | ->groupBy('e.id') |
||
2114 | ->setMaxResults($limit); |
||
2115 | |||
2116 | if (!$canViewOthers) { |
||
2117 | $q->andWhere('e.created_by = :userId') |
||
2118 | ->setParameter('userId', $this->userHelper->getUser()->getId()); |
||
2119 | } |
||
2120 | |||
2121 | $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); |
||
2122 | $chartQuery->applyFilters($q, $filters); |
||
2123 | |||
2124 | if (isset($options['groupBy']) && 'sends' == $options['groupBy']) { |
||
2125 | $chartQuery->applyDateFilters($q, 'date_sent'); |
||
2126 | } |
||
2127 | |||
2128 | if (isset($options['groupBy']) && 'reads' == $options['groupBy']) { |
||
2129 | $chartQuery->applyDateFilters($q, 'date_read'); |
||
2130 | } |
||
2131 | |||
2132 | return $q->execute()->fetchAll(); |
||
2133 | } |
||
2134 | |||
2135 | /** |
||
2136 | * Get a list of emails in a date range. |
||
2137 | * |
||
2138 | * @param int $limit |
||
2139 | * @param \DateTime $dateFrom |
||
2140 | * @param \DateTime $dateTo |
||
2141 | * @param array $filters |
||
2142 | * @param array $options |
||
2143 | * |
||
2144 | * @return array |
||
2145 | */ |
||
2146 | public function getEmailList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = []) |
||
2147 | { |
||
2148 | $canViewOthers = empty($options['canViewOthers']) ? false : $options['canViewOthers']; |
||
2149 | $q = $this->em->getConnection()->createQueryBuilder(); |
||
2150 | $q->select('t.id, t.name, t.date_added, t.date_modified') |
||
2151 | ->from(MAUTIC_TABLE_PREFIX.'emails', 't') |
||
2152 | ->setMaxResults($limit); |
||
2153 | |||
2154 | if (!$canViewOthers) { |
||
2155 | $q->andWhere('t.created_by = :userId') |
||
2156 | ->setParameter('userId', $this->userHelper->getUser()->getId()); |
||
2157 | } |
||
2158 | |||
2159 | $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); |
||
2160 | $chartQuery->applyFilters($q, $filters); |
||
2161 | $chartQuery->applyDateFilters($q, 'date_added'); |
||
2162 | |||
2163 | return $q->execute()->fetchAll(); |
||
2164 | } |
||
2165 | |||
2166 | /** |
||
2167 | * Get a list of upcoming emails. |
||
2168 | * |
||
2169 | * @param int $limit |
||
2170 | * @param bool $canViewOthers |
||
2171 | * |
||
2172 | * @return array |
||
2173 | */ |
||
2174 | public function getUpcomingEmails($limit = 10, $canViewOthers = true) |
||
2175 | { |
||
2176 | /** @var \Mautic\CampaignBundle\Entity\LeadEventLogRepository $leadEventLogRepository */ |
||
2177 | $leadEventLogRepository = $this->em->getRepository('MauticCampaignBundle:LeadEventLog'); |
||
2178 | $leadEventLogRepository->setCurrentUser($this->userHelper->getUser()); |
||
2179 | |||
2180 | return $leadEventLogRepository->getUpcomingEvents( |
||
2181 | [ |
||
2182 | 'type' => 'email.send', |
||
2183 | 'limit' => $limit, |
||
2184 | 'canViewOthers' => $canViewOthers, |
||
2185 | ] |
||
2186 | ); |
||
2187 | } |
||
2188 | |||
2189 | /** |
||
2190 | * @param $type |
||
2191 | * @param string $filter |
||
2192 | * @param int $limit |
||
2193 | * @param int $start |
||
2194 | * @param array $options |
||
2195 | * |
||
2196 | * @return array |
||
2197 | */ |
||
2198 | public function getLookupResults($type, $filter = '', $limit = 10, $start = 0, $options = []) |
||
2199 | { |
||
2200 | $results = []; |
||
2201 | switch ($type) { |
||
2202 | case 'email': |
||
2203 | $emailRepo = $this->getRepository(); |
||
2204 | $emailRepo->setCurrentUser($this->userHelper->getUser()); |
||
2205 | $emails = $emailRepo->getEmailList( |
||
2206 | $filter, |
||
2207 | $limit, |
||
2208 | $start, |
||
2209 | $this->security->isGranted('email:emails:viewother'), |
||
2210 | isset($options['top_level']) ? $options['top_level'] : false, |
||
2211 | isset($options['email_type']) ? $options['email_type'] : null, |
||
2212 | isset($options['ignore_ids']) ? $options['ignore_ids'] : [], |
||
2213 | isset($options['variant_parent']) ? $options['variant_parent'] : null |
||
2214 | ); |
||
2215 | |||
2216 | foreach ($emails as $email) { |
||
2217 | $results[$email['language']][$email['id']] = $email['name']; |
||
2218 | } |
||
2219 | |||
2220 | //sort by language |
||
2221 | ksort($results); |
||
2222 | |||
2223 | break; |
||
2224 | } |
||
2225 | |||
2226 | return $results; |
||
2227 | } |
||
2228 | |||
2229 | /** |
||
2230 | * @param $sendTo |
||
2231 | */ |
||
2232 | private function getContactCompanies(array &$sendTo) |
||
2233 | { |
||
2234 | $fetchCompanies = []; |
||
2235 | foreach ($sendTo as $key => $contact) { |
||
2236 | if (!isset($contact['companies'])) { |
||
2237 | $fetchCompanies[$contact['id']] = $key; |
||
2238 | $sendTo[$key]['companies'] = []; |
||
2239 | } |
||
2240 | } |
||
2241 | |||
2242 | if (!empty($fetchCompanies)) { |
||
2243 | // Simple dbal query that fetches lead_id IN $fetchCompanies and returns as array |
||
2244 | $companies = $this->companyModel->getRepository()->getCompaniesForContacts(array_keys($fetchCompanies)); |
||
2245 | |||
2246 | foreach ($companies as $contactId => $contactCompanies) { |
||
2247 | $key = $fetchCompanies[$contactId]; |
||
2248 | $sendTo[$key]['companies'] = $contactCompanies; |
||
2249 | } |
||
2250 | } |
||
2251 | } |
||
2252 | |||
2253 | /** |
||
2254 | * Send an email to lead(s). |
||
2255 | * |
||
2256 | * @param $email |
||
2257 | * @param $users |
||
2258 | * @param mixed $leadFields |
||
2259 | * @param array $tokens |
||
2260 | * @param array $assetAttachments |
||
2261 | * @param bool $saveStat |
||
2262 | * |
||
2263 | * @return mixed |
||
2264 | * |
||
2265 | * @throws \Doctrine\ORM\ORMException |
||
2266 | */ |
||
2267 | public function sendSampleEmailToUser($email, $users, $leadFields = null, $tokens = [], $assetAttachments = [], $saveStat = true) |
||
2268 | { |
||
2269 | if (!$emailId = $email->getId()) { |
||
2270 | return false; |
||
2271 | } |
||
2272 | |||
2273 | if (!is_array($users)) { |
||
2274 | $user = ['id' => $users]; |
||
2275 | $users = [$user]; |
||
2276 | } |
||
2277 | |||
2278 | //get email settings |
||
2279 | $emailSettings = &$this->getEmailSettings($email, false); |
||
2280 | |||
2281 | //noone to send to so bail |
||
2282 | if (empty($users)) { |
||
2283 | return false; |
||
2284 | } |
||
2285 | |||
2286 | $mailer = $this->mailHelper->getSampleMailer(); |
||
2287 | $mailer->setLead($leadFields, true); |
||
2288 | $mailer->setTokens($tokens); |
||
2289 | $mailer->setEmail($email, false, $emailSettings[$emailId]['slots'], $assetAttachments, (!$saveStat)); |
||
2290 | |||
2291 | $errors = []; |
||
2292 | foreach ($users as $user) { |
||
2293 | $idHash = uniqid(); |
||
2294 | $mailer->setIdHash($idHash, $saveStat); |
||
2295 | |||
2296 | if (!is_array($user)) { |
||
2297 | $id = $user; |
||
2298 | $user = ['id' => $id]; |
||
2299 | } else { |
||
2300 | $id = $user['id']; |
||
2301 | } |
||
2302 | |||
2303 | if (!isset($user['email'])) { |
||
2304 | $userEntity = $this->userModel->getEntity($id); |
||
2305 | $user['email'] = $userEntity->getEmail(); |
||
2306 | $user['firstname'] = $userEntity->getFirstName(); |
||
2307 | $user['lastname'] = $userEntity->getLastName(); |
||
2308 | } |
||
2309 | |||
2310 | if (!$mailer->setTo($user['email'], $user['firstname'].' '.$user['lastname'])) { |
||
2311 | $errors[] = "{$user['email']}: ".$this->translator->trans('mautic.email.bounce.reason.bad_email'); |
||
2312 | } else { |
||
2313 | if (!$mailer->queue(true)) { |
||
2314 | $errorArray = $mailer->getErrors(); |
||
2315 | unset($errorArray['failures']); |
||
2316 | $errors[] = "{$user['email']}: ".implode('; ', $errorArray); |
||
2317 | } |
||
2318 | |||
2319 | if ($saveStat) { |
||
2320 | $saveEntities[] = $mailer->createEmailStat(false, $user['email']); |
||
2321 | } |
||
2322 | } |
||
2323 | } |
||
2324 | |||
2325 | //flush the message |
||
2326 | if (!$mailer->flushQueue()) { |
||
2327 | $errorArray = $mailer->getErrors(); |
||
2328 | unset($errorArray['failures']); |
||
2329 | $errors[] = implode('; ', $errorArray); |
||
2330 | } |
||
2331 | |||
2332 | if (isset($saveEntities)) { |
||
2333 | $this->getStatRepository()->saveEntities($saveEntities); |
||
2334 | } |
||
2335 | |||
2336 | //save some memory |
||
2337 | unset($mailer); |
||
2338 | |||
2339 | return $errors; |
||
2340 | } |
||
2341 | |||
2342 | /** |
||
2343 | * @param $segmentId |
||
2344 | * |
||
2345 | * @return array |
||
2346 | */ |
||
2347 | public function getEmailsIdsWithDependenciesOnSegment($segmentId) |
||
2348 | { |
||
2349 | $entities = $this->getEntities( |
||
2350 | [ |
||
2351 | 'filter' => [ |
||
2352 | 'force' => [ |
||
2353 | [ |
||
2354 | 'column' => 'l.id', |
||
2355 | 'expr' => 'eq', |
||
2356 | 'value' => $segmentId, |
||
2357 | ], |
||
2358 | ], |
||
2359 | ], |
||
2360 | ] |
||
2361 | ); |
||
2362 | |||
2363 | $ids = []; |
||
2364 | foreach ($entities as $entity) { |
||
2365 | $ids[] = $entity->getId(); |
||
2366 | } |
||
2367 | |||
2368 | return $ids; |
||
2369 | } |
||
2370 | } |
||
2371 |