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\CampaignBundle\EventListener; |
||
13 | |||
14 | use Doctrine\Common\Collections\ArrayCollection; |
||
15 | use Doctrine\ORM\EntityManager; |
||
16 | use Mautic\CampaignBundle\Entity\Campaign; |
||
17 | use Mautic\CampaignBundle\Entity\Lead as CampaignLead; |
||
18 | use Mautic\CampaignBundle\Entity\LeadEventLog; |
||
19 | use Mautic\CampaignBundle\Entity\LeadEventLogRepository; |
||
20 | use Mautic\CampaignBundle\Entity\LeadRepository; |
||
21 | use Mautic\CampaignBundle\EventCollector\EventCollector; |
||
22 | use Mautic\CampaignBundle\Membership\MembershipManager; |
||
23 | use Mautic\CampaignBundle\Model\CampaignModel; |
||
24 | use Mautic\CoreBundle\Security\Permissions\CorePermissions; |
||
25 | use Mautic\LeadBundle\Entity\Lead; |
||
26 | use Mautic\LeadBundle\Entity\LeadList; |
||
27 | use Mautic\LeadBundle\Entity\LeadListRepository; |
||
28 | use Mautic\LeadBundle\Event\LeadMergeEvent; |
||
29 | use Mautic\LeadBundle\Event\LeadTimelineEvent; |
||
30 | use Mautic\LeadBundle\Event\ListChangeEvent; |
||
31 | use Mautic\LeadBundle\LeadEvents; |
||
32 | use Mautic\LeadBundle\Model\LeadModel; |
||
33 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
||
34 | use Symfony\Component\Routing\RouterInterface; |
||
35 | use Symfony\Component\Translation\TranslatorInterface; |
||
36 | |||
37 | class LeadSubscriber implements EventSubscriberInterface |
||
38 | { |
||
39 | /** |
||
40 | * @var array |
||
41 | */ |
||
42 | private $campaignLists; |
||
43 | |||
44 | /** |
||
45 | * @var array |
||
46 | */ |
||
47 | private $campaignReferences; |
||
48 | |||
49 | /** |
||
50 | * @var MembershipManager |
||
51 | */ |
||
52 | private $membershipManager; |
||
53 | |||
54 | /** |
||
55 | * @var EventCollector |
||
56 | */ |
||
57 | private $eventCollector; |
||
58 | |||
59 | /** |
||
60 | * @var CampaignModel |
||
61 | */ |
||
62 | private $campaignModel; |
||
63 | |||
64 | /** |
||
65 | * @var LeadModel |
||
66 | */ |
||
67 | private $leadModel; |
||
68 | |||
69 | /** |
||
70 | * @var TranslatorInterface |
||
71 | */ |
||
72 | private $translator; |
||
73 | |||
74 | /** |
||
75 | * @var EntityManager |
||
76 | */ |
||
77 | private $entityManager; |
||
78 | |||
79 | /** |
||
80 | * @var RouterInterface |
||
81 | */ |
||
82 | private $router; |
||
83 | |||
84 | /** |
||
85 | * @var CorePermissions |
||
86 | */ |
||
87 | private $security; |
||
88 | |||
89 | /** |
||
90 | * @var LeadListRepository |
||
91 | */ |
||
92 | private $segmentRepository; |
||
93 | |||
94 | /** |
||
95 | * @var LeadEventLogRepository |
||
96 | */ |
||
97 | private $contactEventLogRepository; |
||
98 | |||
99 | /** |
||
100 | * @var LeadRepository |
||
101 | */ |
||
102 | private $contactRepository; |
||
103 | |||
104 | public function __construct( |
||
105 | MembershipManager $membershipManager, |
||
106 | EventCollector $eventCollector, |
||
107 | CampaignModel $campaignModel, |
||
108 | LeadModel $leadModel, |
||
109 | TranslatorInterface $translator, |
||
110 | EntityManager $entityManager, |
||
111 | RouterInterface $router, |
||
112 | CorePermissions $security |
||
113 | ) { |
||
114 | $this->membershipManager = $membershipManager; |
||
115 | $this->eventCollector = $eventCollector; |
||
116 | $this->campaignModel = $campaignModel; |
||
117 | $this->leadModel = $leadModel; |
||
118 | $this->translator = $translator; |
||
119 | $this->entityManager = $entityManager; |
||
120 | $this->router = $router; |
||
121 | $this->security = $security; |
||
122 | $this->segmentRepository = $entityManager->getRepository(LeadList::class); |
||
123 | $this->contactEventLogRepository = $entityManager->getRepository(LeadEventLog::class); |
||
124 | $this->contactRepository = $entityManager->getRepository(CampaignLead::class); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * @return array |
||
129 | */ |
||
130 | public static function getSubscribedEvents() |
||
131 | { |
||
132 | return [ |
||
133 | LeadEvents::LEAD_LIST_BATCH_CHANGE => ['onLeadListBatchChange', 0], |
||
134 | LeadEvents::LEAD_LIST_CHANGE => ['onLeadListChange', 0], |
||
135 | LeadEvents::TIMELINE_ON_GENERATE => ['onTimelineGenerate', 0], |
||
136 | LeadEvents::LEAD_POST_MERGE => ['onLeadMerge', 0], |
||
137 | ]; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Add/remove leads from campaigns based on batch lead list changes. |
||
142 | */ |
||
143 | public function onLeadListBatchChange(ListChangeEvent $event) |
||
144 | { |
||
145 | $leads = $event->getLeads(); |
||
146 | $list = $event->getList(); |
||
147 | $action = $event->wasAdded() ? 'added' : 'removed'; |
||
148 | $listCampaigns = []; |
||
149 | |||
150 | //get campaigns for the list |
||
151 | $listCampaigns[$list->getId()] = $this->campaignModel->getRepository()->getPublishedCampaignsByLeadLists($list->getId()); |
||
152 | |||
153 | $leadLists = $this->segmentRepository->getLeadLists($leads, true, true); |
||
154 | |||
155 | if (!empty($listCampaigns[$list->getId()])) { |
||
156 | $contactCollection = new ArrayCollection(); |
||
157 | foreach ($leads as $lead) { |
||
158 | $id['id'] = $lead['id'] ?? $lead; // getReference needs this to be [identifier => value] |
||
159 | $contactCollection->set($id['id'], $this->entityManager->getReference(Lead::class, $id)); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
160 | } |
||
161 | |||
162 | foreach ($listCampaigns[$list->getId()] as $c) { |
||
163 | if (!isset($this->campaignReferences[$c['id']])) { |
||
164 | $this->campaignReferences[$c['id']] = $this->entityManager->getReference(Campaign::class, $c['id']); |
||
165 | } |
||
166 | |||
167 | if ('added' == $action) { |
||
168 | $this->membershipManager->addContacts($contactCollection, $this->campaignReferences[$c['id']], false); |
||
169 | } else { |
||
170 | if (!isset($this->campaignLists[$c['id']])) { |
||
171 | $this->campaignLists[$c['id']] = []; |
||
172 | foreach ($c['lists'] as $l) { |
||
173 | $this->campaignLists[$c['id']][] = $l['id']; |
||
174 | } |
||
175 | } |
||
176 | |||
177 | $removeContacts = new ArrayCollection(); |
||
178 | foreach ($contactCollection as $id => $contact) { |
||
179 | $lists = $leadLists[$id] ?? []; |
||
180 | if (array_intersect(array_keys($lists), $this->campaignLists[$c['id']])) { |
||
181 | continue; |
||
182 | } else { |
||
183 | $removeContacts->set($id, $contact); |
||
184 | } |
||
185 | } |
||
186 | |||
187 | $this->membershipManager->removeContacts($removeContacts, $this->campaignReferences[$c['id']], true); |
||
188 | } |
||
189 | } |
||
190 | $this->entityManager->clear(Lead::class); |
||
191 | } |
||
192 | |||
193 | // Save memory with batch processing |
||
194 | unset($event, $leads, $list, $listCampaigns, $leadLists); |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Add/remove leads from campaigns based on lead list changes. |
||
199 | */ |
||
200 | public function onLeadListChange(ListChangeEvent $event) |
||
201 | { |
||
202 | $lead = $event->getLead(); |
||
203 | $list = $event->getList(); |
||
204 | $action = $event->wasAdded() ? 'added' : 'removed'; |
||
205 | |||
206 | //get campaigns for the list |
||
207 | $listCampaigns = $this->campaignModel->getRepository()->getPublishedCampaignsByLeadLists( |
||
208 | $list->getId(), |
||
209 | $this->security->isGranted('campaign:campaigns:viewother') |
||
210 | ); |
||
211 | |||
212 | $leadLists = $this->leadModel->getLists($lead, true); |
||
213 | $leadListIds = array_keys($leadLists); |
||
214 | $campaignLists = []; |
||
215 | |||
216 | // If the lead was removed then don't count it |
||
217 | if ('removed' == $action) { |
||
218 | $key = array_search($list->getId(), $leadListIds); |
||
219 | unset($leadListIds[$key]); |
||
220 | } |
||
221 | |||
222 | if (!empty($listCampaigns)) { |
||
223 | foreach ($listCampaigns as $c) { |
||
224 | /** @var Campaign $campaign */ |
||
225 | $campaign = $this->entityManager->getReference(Campaign::class, $c['id']); |
||
226 | |||
227 | if (!isset($campaignLists[$c['id']])) { |
||
228 | $campaignLists[$c['id']] = array_keys($c['lists']); |
||
229 | } |
||
230 | |||
231 | if ('added' == $action) { |
||
232 | $this->membershipManager->addContact($lead, $campaign, false); |
||
233 | } else { |
||
234 | if (array_intersect($leadListIds, $campaignLists[$c['id']])) { |
||
235 | continue; |
||
236 | } |
||
237 | |||
238 | $this->membershipManager->removeContact($lead, $campaign, true); |
||
239 | } |
||
240 | |||
241 | unset($campaign); |
||
242 | } |
||
243 | } |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Compile events for the lead timeline. |
||
248 | */ |
||
249 | public function onTimelineGenerate(LeadTimelineEvent $event) |
||
250 | { |
||
251 | $this->addTimelineEvents($event, 'campaign.event', $this->translator->trans('mautic.campaign.triggered')); |
||
252 | $this->addTimelineEvents($event, 'campaign.event.scheduled', $this->translator->trans('mautic.campaign.scheduled')); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Update records after lead merge. |
||
257 | */ |
||
258 | public function onLeadMerge(LeadMergeEvent $event) |
||
259 | { |
||
260 | $this->contactEventLogRepository->updateLead($event->getLoser()->getId(), $event->getVictor()->getId()); |
||
261 | $this->contactRepository->updateLead($event->getLoser()->getId(), $event->getVictor()->getId()); |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * @param $eventTypeKey |
||
266 | * @param $eventTypeName |
||
267 | */ |
||
268 | private function addTimelineEvents(LeadTimelineEvent $event, $eventTypeKey, $eventTypeName) |
||
269 | { |
||
270 | $event->addEventType($eventTypeKey, $eventTypeName); |
||
271 | $event->addSerializerGroup('campaignList'); |
||
272 | |||
273 | // Decide if those events are filtered |
||
274 | if (!$event->isApplicable($eventTypeKey)) { |
||
275 | return; |
||
276 | } |
||
277 | |||
278 | $options = $event->getQueryOptions(); |
||
279 | $options['scheduledState'] = ('campaign.event' === $eventTypeKey) ? false : true; |
||
280 | $logs = $this->contactEventLogRepository->getLeadLogs($event->getLeadId(), $options); |
||
281 | $eventSettings = $this->eventCollector->getEventsArray(); |
||
282 | |||
283 | // Add total number to counter |
||
284 | $event->addToCounter($eventTypeKey, $logs); |
||
285 | |||
286 | if (!$event->isEngagementCount()) { |
||
287 | foreach ($logs['results'] as $log) { |
||
288 | $template = (!empty($eventSettings['action'][$log['type']]['timelineTemplate'])) |
||
289 | ? $eventSettings['action'][$log['type']]['timelineTemplate'] : 'MauticCampaignBundle:SubscribedEvents\Timeline:index.html.php'; |
||
290 | |||
291 | $label = $log['event_name'].' / '.$log['campaign_name']; |
||
292 | |||
293 | if (empty($log['isScheduled']) && empty($log['dateTriggered'])) { |
||
294 | // Note as cancelled |
||
295 | $label .= ' <i data-toggle="tooltip" title="'.$this->translator->trans('mautic.campaign.event.cancelled') |
||
296 | .'" class="fa fa-calendar-times-o text-warning timeline-campaign-event-cancelled-'.$log['event_id'].'"></i>'; |
||
297 | } |
||
298 | |||
299 | if ((!empty($log['metadata']['errors']) && empty($log['dateTriggered'])) || !empty($log['metadata']['failed']) || !empty($log['fail_reason'])) { |
||
300 | $label .= ' <i data-toggle="tooltip" title="'.$this->translator->trans('mautic.campaign.event.has_last_attempt_error') |
||
301 | .'" class="fa fa-warning text-danger"></i>'; |
||
302 | } |
||
303 | |||
304 | $extra = [ |
||
305 | 'log' => $log, |
||
306 | ]; |
||
307 | |||
308 | if ($event->isForTimeline()) { |
||
309 | $extra['campaignEventSettings'] = $eventSettings; |
||
310 | } |
||
311 | |||
312 | $event->addEvent( |
||
313 | [ |
||
314 | 'event' => $eventTypeKey, |
||
315 | 'eventId' => $eventTypeKey.$log['log_id'], |
||
316 | 'eventLabel' => [ |
||
317 | 'label' => $label, |
||
318 | 'href' => $this->router->generate( |
||
319 | 'mautic_campaign_action', |
||
320 | ['objectAction' => 'view', 'objectId' => $log['campaign_id']] |
||
321 | ), |
||
322 | ], |
||
323 | 'eventType' => $eventTypeName, |
||
324 | 'timestamp' => $log['dateTriggered'], |
||
325 | 'extra' => $extra, |
||
326 | 'contentTemplate' => $template, |
||
327 | 'icon' => 'fa-clock-o', |
||
328 | 'contactId' => $log['lead_id'], |
||
329 | ] |
||
330 | ); |
||
331 | } |
||
332 | } |
||
333 | } |
||
334 | } |
||
335 |