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; |
||||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
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); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||||||
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); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
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); |
||||||
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) |
||||||
0 ignored issues
–
show
The parameter
$withBC is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
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); |
||||||
0 ignored issues
–
show
It seems like
$dateTo can also be of type null ; however, parameter $dateTo of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
It seems like
$dateFrom can also be of type null ; however, parameter $dateFrom of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
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) { |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
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)) { |
||||||
0 ignored issues
–
show
It seems like
$ids can also be of type null ; however, parameter $haystack of in_array() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
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); |
||||||
0 ignored issues
–
show
It seems like
$totalLeadCount can also be of type array ; however, parameter $max of Symfony\Component\Consol...gressBar::__construct() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
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); |
||||||
0 ignored issues
–
show
It seems like
$leads can also be of type integer ; however, parameter $value of count() does only seem to accept Countable|array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
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); |
||||||
0 ignored issues
–
show
It seems like
$listErrors can also be of type boolean ; however, parameter $value of count() does only seem to accept Countable|array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
1093 | |||||||
1094 | $sentCount -= $listFailedCount; |
||||||
1095 | $failedCount += $listFailedCount; |
||||||
1096 | |||||||
1097 | $failedRecipientsByList[$options['listId']] = $listErrors; |
||||||
1098 | } |
||||||
1099 | |||||||
1100 | if ($batch) { |
||||||
1101 | if ($progress) { |
||||||
1102 | $progressCounter += $leadCount; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
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() |
||||||
0 ignored issues
–
show
|
|||||||
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) { |
||||||
0 ignored issues
–
show
|
|||||||
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), |
||||||
0 ignored issues
–
show
It seems like
$this->em->getReference(...eadBundle:Lead', $lead) can also be of type object ; however, parameter $contactId of Mautic\LeadBundle\Model\...act::addDncForContact() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
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 |