Issues (3627)

CampaignBundle/EventListener/LeadSubscriber.php (1 issue)

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
The variable $id seems to be defined later in this foreach loop on line 158. Are you sure it is defined here?
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