LeadModel::import()   F
last analyzed

Complexity

Conditions 65
Paths > 20000

Size

Total Lines 279
Code Lines 166

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 65
eloc 166
c 0
b 0
f 0
nc 1158119424
nop 8
dl 0
loc 279
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\LeadBundle\Model;
13
14
use Doctrine\ORM\NonUniqueResultException;
15
use Doctrine\ORM\Tools\Pagination\Paginator;
16
use Mautic\CategoryBundle\Entity\Category;
17
use Mautic\CategoryBundle\Model\CategoryModel;
18
use Mautic\ChannelBundle\Helper\ChannelListHelper;
19
use Mautic\CoreBundle\Entity\IpAddress;
20
use Mautic\CoreBundle\Form\RequestTrait;
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\CookieHelper;
25
use Mautic\CoreBundle\Helper\CoreParametersHelper;
26
use Mautic\CoreBundle\Helper\DateTimeHelper;
27
use Mautic\CoreBundle\Helper\InputHelper;
28
use Mautic\CoreBundle\Helper\IpLookupHelper;
29
use Mautic\CoreBundle\Helper\PathsHelper;
30
use Mautic\CoreBundle\Model\FormModel;
31
use Mautic\EmailBundle\Helper\EmailValidator;
32
use Mautic\LeadBundle\DataObject\LeadManipulator;
33
use Mautic\LeadBundle\Entity\Company;
34
use Mautic\LeadBundle\Entity\CompanyChangeLog;
35
use Mautic\LeadBundle\Entity\CompanyLead;
36
use Mautic\LeadBundle\Entity\DoNotContact as DNC;
37
use Mautic\LeadBundle\Entity\FrequencyRule;
38
use Mautic\LeadBundle\Entity\Lead;
39
use Mautic\LeadBundle\Entity\LeadCategory;
40
use Mautic\LeadBundle\Entity\LeadEventLog;
41
use Mautic\LeadBundle\Entity\LeadField;
42
use Mautic\LeadBundle\Entity\LeadList;
43
use Mautic\LeadBundle\Entity\LeadRepository;
44
use Mautic\LeadBundle\Entity\OperatorListTrait;
45
use Mautic\LeadBundle\Entity\PointsChangeLog;
46
use Mautic\LeadBundle\Entity\StagesChangeLog;
47
use Mautic\LeadBundle\Entity\Tag;
48
use Mautic\LeadBundle\Entity\UtmTag;
49
use Mautic\LeadBundle\Event\CategoryChangeEvent;
50
use Mautic\LeadBundle\Event\DoNotContactAddEvent;
51
use Mautic\LeadBundle\Event\DoNotContactRemoveEvent;
52
use Mautic\LeadBundle\Event\LeadEvent;
53
use Mautic\LeadBundle\Event\LeadTimelineEvent;
54
use Mautic\LeadBundle\Exception\ImportFailedException;
55
use Mautic\LeadBundle\Form\Type\LeadType;
56
use Mautic\LeadBundle\Helper\ContactRequestHelper;
57
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
58
use Mautic\LeadBundle\LeadEvents;
59
use Mautic\LeadBundle\Tracker\ContactTracker;
60
use Mautic\LeadBundle\Tracker\DeviceTracker;
61
use Mautic\PluginBundle\Helper\IntegrationHelper;
62
use Mautic\StageBundle\Entity\Stage;
63
use Mautic\UserBundle\Entity\User;
64
use Mautic\UserBundle\Security\Provider\UserProvider;
65
use Symfony\Component\EventDispatcher\Event;
66
use Symfony\Component\Form\FormFactory;
67
use Symfony\Component\HttpFoundation\RequestStack;
68
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
69
use Symfony\Component\Intl\Intl;
70
71
/**
72
 * Class LeadModel
73
 * {@inheritdoc}
74
 */
75
class LeadModel extends FormModel
76
{
77
    use DefaultValueTrait;
78
    use OperatorListTrait;
79
    use RequestTrait;
80
81
    const CHANNEL_FEATURE = 'contact_preference';
82
83
    /**
84
     * @var \Symfony\Component\HttpFoundation\Request|null
85
     */
86
    protected $request;
87
88
    /**
89
     * @var CookieHelper
90
     */
91
    protected $cookieHelper;
92
93
    /**
94
     * @var IpLookupHelper
95
     */
96
    protected $ipLookupHelper;
97
98
    /**
99
     * @var PathsHelper
100
     */
101
    protected $pathsHelper;
102
103
    /**
104
     * @var IntegrationHelper
105
     */
106
    protected $integrationHelper;
107
108
    /**
109
     * @var FieldModel
110
     */
111
    protected $leadFieldModel;
112
113
    /**
114
     * @var array
115
     */
116
    protected $leadFields = [];
117
118
    /**
119
     * @var ListModel
120
     */
121
    protected $leadListModel;
122
123
    /**
124
     * @var CompanyModel
125
     */
126
    protected $companyModel;
127
128
    /**
129
     * @var CategoryModel
130
     */
131
    protected $categoryModel;
132
133
    /**
134
     * @var FormFactory
135
     */
136
    protected $formFactory;
137
138
    /**
139
     * @var ChannelListHelper
140
     */
141
    protected $channelListHelper;
142
143
    /**
144
     * @var CoreParametersHelper
145
     */
146
    protected $coreParametersHelper;
147
148
    /**
149
     * @var UserProvider
150
     */
151
    protected $userProvider;
152
153
    protected $leadTrackingId;
154
155
    /**
156
     * @var bool
157
     */
158
    protected $leadTrackingCookieGenerated = false;
159
160
    /**
161
     * @var array
162
     */
163
    protected $availableLeadFields = [];
164
165
    /**
166
     * @var EmailValidator
167
     */
168
    protected $emailValidator;
169
170
    /**
171
     * @var ContactTracker
172
     */
173
    private $contactTracker;
174
175
    /**
176
     * @var DeviceTracker
177
     */
178
    private $deviceTracker;
179
180
    /**
181
     * @var LegacyLeadModel
182
     */
183
    private $legacyLeadModel;
184
185
    /**
186
     * @var IpAddressModel
187
     */
188
    private $ipAddressModel;
189
190
    /**
191
     * @var bool
192
     */
193
    private $repoSetup = false;
194
195
    /**
196
     * @var array
197
     */
198
    private $flattenedFields = [];
199
200
    /**
201
     * @var array
202
     */
203
    private $fieldsByGroup = [];
204
205
    public function __construct(
206
        RequestStack $requestStack,
207
        CookieHelper $cookieHelper,
208
        IpLookupHelper $ipLookupHelper,
209
        PathsHelper $pathsHelper,
210
        IntegrationHelper $integrationHelper,
211
        FieldModel $leadFieldModel,
212
        ListModel $leadListModel,
213
        FormFactory $formFactory,
214
        CompanyModel $companyModel,
215
        CategoryModel $categoryModel,
216
        ChannelListHelper $channelListHelper,
217
        CoreParametersHelper $coreParametersHelper,
218
        EmailValidator $emailValidator,
219
        UserProvider $userProvider,
220
        ContactTracker $contactTracker,
221
        DeviceTracker $deviceTracker,
222
        LegacyLeadModel $legacyLeadModel,
223
        IpAddressModel $ipAddressModel
224
    ) {
225
        $this->request              = $requestStack->getCurrentRequest();
226
        $this->cookieHelper         = $cookieHelper;
227
        $this->ipLookupHelper       = $ipLookupHelper;
228
        $this->pathsHelper          = $pathsHelper;
229
        $this->integrationHelper    = $integrationHelper;
230
        $this->leadFieldModel       = $leadFieldModel;
231
        $this->leadListModel        = $leadListModel;
232
        $this->companyModel         = $companyModel;
233
        $this->formFactory          = $formFactory;
234
        $this->categoryModel        = $categoryModel;
235
        $this->channelListHelper    = $channelListHelper;
236
        $this->coreParametersHelper = $coreParametersHelper;
237
        $this->emailValidator       = $emailValidator;
238
        $this->userProvider         = $userProvider;
239
        $this->contactTracker       = $contactTracker;
240
        $this->deviceTracker        = $deviceTracker;
241
        $this->legacyLeadModel      = $legacyLeadModel;
242
        $this->ipAddressModel       = $ipAddressModel;
243
    }
244
245
    /**
246
     * @return LeadRepository
247
     */
248
    public function getRepository()
249
    {
250
        /** @var LeadRepository $repo */
251
        $repo = $this->em->getRepository(Lead::class);
252
        $repo->setDispatcher($this->dispatcher);
253
254
        if (!$this->repoSetup) {
255
            $this->repoSetup = true;
256
257
            //set the point trigger model in order to get the color code for the lead
258
            $fields = $this->leadFieldModel->getFieldList(true, false);
259
260
            $socialFields = (!empty($fields['social'])) ? array_keys($fields['social']) : [];
261
            $repo->setAvailableSocialFields($socialFields);
262
263
            $searchFields = [];
264
            foreach ($fields as $groupFields) {
265
                $searchFields = array_merge($searchFields, array_keys($groupFields));
266
            }
267
            $repo->setAvailableSearchFields($searchFields);
268
        }
269
270
        return $repo;
271
    }
272
273
    /**
274
     * Get the tags repository.
275
     *
276
     * @return \Mautic\LeadBundle\Entity\TagRepository
277
     */
278
    public function getTagRepository()
279
    {
280
        return $this->em->getRepository('MauticLeadBundle:Tag');
281
    }
282
283
    /**
284
     * @return \Mautic\LeadBundle\Entity\PointsChangeLogRepository
285
     */
286
    public function getPointLogRepository()
287
    {
288
        return $this->em->getRepository('MauticLeadBundle:PointsChangeLog');
289
    }
290
291
    /**
292
     * Get the tags repository.
293
     *
294
     * @return \Mautic\LeadBundle\Entity\UtmTagRepository
295
     */
296
    public function getUtmTagRepository()
297
    {
298
        return $this->em->getRepository('MauticLeadBundle:UtmTag');
299
    }
300
301
    /**
302
     * Get the tags repository.
303
     *
304
     * @return \Mautic\LeadBundle\Entity\LeadDeviceRepository
305
     */
306
    public function getDeviceRepository()
307
    {
308
        return $this->em->getRepository('MauticLeadBundle:LeadDevice');
309
    }
310
311
    /**
312
     * Get the lead event log repository.
313
     *
314
     * @return \Mautic\LeadBundle\Entity\LeadEventLogRepository
315
     */
316
    public function getEventLogRepository()
317
    {
318
        return $this->em->getRepository('MauticLeadBundle:LeadEventLog');
319
    }
320
321
    /**
322
     * Get the frequency rules repository.
323
     *
324
     * @return \Mautic\LeadBundle\Entity\FrequencyRuleRepository
325
     */
326
    public function getFrequencyRuleRepository()
327
    {
328
        return $this->em->getRepository('MauticLeadBundle:FrequencyRule');
329
    }
330
331
    /**
332
     * Get the Stages change log repository.
333
     *
334
     * @return \Mautic\LeadBundle\Entity\StagesChangeLogRepository
335
     */
336
    public function getStagesChangeLogRepository()
337
    {
338
        return $this->em->getRepository('MauticLeadBundle:StagesChangeLog');
339
    }
340
341
    /**
342
     * Get the lead categories repository.
343
     *
344
     * @return \Mautic\LeadBundle\Entity\LeadCategoryRepository
345
     */
346
    public function getLeadCategoryRepository()
347
    {
348
        return $this->em->getRepository('MauticLeadBundle:LeadCategory');
349
    }
350
351
    /**
352
     * @return \Mautic\LeadBundle\Entity\MergeRecordRepository
353
     */
354
    public function getMergeRecordRepository()
355
    {
356
        return $this->em->getRepository('MauticLeadBundle:MergeRecord');
357
    }
358
359
    /**
360
     * @return LeadListRepository
0 ignored issues
show
Bug introduced by
The type Mautic\LeadBundle\Model\LeadListRepository was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
361
     */
362
    public function getLeadListRepository()
363
    {
364
        return $this->em->getRepository('MauticLeadBundle:LeadList');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->em->getRep...icLeadBundle:LeadList') returns the type Doctrine\Common\Persistence\ObjectRepository which is incompatible with the documented return type Mautic\LeadBundle\Model\LeadListRepository.
Loading history...
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     *
370
     * @return string
371
     */
372
    public function getPermissionBase()
373
    {
374
        return 'lead:leads';
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     *
380
     * @return string
381
     */
382
    public function getNameGetter()
383
    {
384
        return 'getPrimaryIdentifier';
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     *
390
     * @param Lead                                $entity
391
     * @param \Symfony\Component\Form\FormFactory $formFactory
392
     * @param string|null                         $action
393
     * @param array                               $options
394
     *
395
     * @return \Symfony\Component\Form\Form
396
     *
397
     * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
398
     */
399
    public function createForm($entity, $formFactory, $action = null, $options = [])
400
    {
401
        if (!$entity instanceof Lead) {
0 ignored issues
show
introduced by
$entity is always a sub-type of Mautic\LeadBundle\Entity\Lead.
Loading history...
402
            throw new MethodNotAllowedHttpException(['Lead'], 'Entity must be of class Lead()');
403
        }
404
        if (!empty($action)) {
405
            $options['action'] = $action;
406
        }
407
408
        return $formFactory->create(LeadType::class, $entity, $options);
409
    }
410
411
    /**
412
     * Get a specific entity or generate a new one if id is empty.
413
     *
414
     * @param $id
415
     *
416
     * @return Lead|null
417
     */
418
    public function getEntity($id = null)
419
    {
420
        if (null === $id) {
421
            return new Lead();
422
        }
423
424
        $entity = parent::getEntity($id);
425
426
        if (null === $entity) {
427
            // Check if this contact was merged into another and if so, return the new contact
428
            if ($entity = $this->getMergeRecordRepository()->findMergedContact($id)) {
429
                // Hydrate fields with custom field data
430
                $fields = $this->getRepository()->getFieldValues($entity->getId());
431
                $entity->setFields($fields);
432
            }
433
        }
434
435
        return $entity;
436
    }
437
438
    /**
439
     * {@inheritdoc}
440
     *
441
     * @param $action
442
     * @param $event
443
     * @param $entity
444
     * @param $isNew
445
     *
446
     * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
447
     */
448
    protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null)
449
    {
450
        if (!$entity instanceof Lead) {
451
            throw new MethodNotAllowedHttpException(['Lead'], 'Entity must be of class Lead()');
452
        }
453
454
        switch ($action) {
455
            case 'pre_save':
456
                $name = LeadEvents::LEAD_PRE_SAVE;
457
                break;
458
            case 'post_save':
459
                $name = LeadEvents::LEAD_POST_SAVE;
460
                break;
461
            case 'pre_delete':
462
                $name = LeadEvents::LEAD_PRE_DELETE;
463
                break;
464
            case 'post_delete':
465
                $name = LeadEvents::LEAD_POST_DELETE;
466
                break;
467
            default:
468
                return null;
469
        }
470
471
        if ($this->dispatcher->hasListeners($name)) {
472
            if (empty($event)) {
473
                $event = new LeadEvent($entity, $isNew);
474
                $event->setEntityManager($this->em);
475
            }
476
            $this->dispatcher->dispatch($name, $event);
477
478
            return $event;
479
        } else {
480
            return null;
481
        }
482
    }
483
484
    /**
485
     * {@inheritdoc}
486
     *
487
     * @param Lead $entity
488
     * @param bool $unlock
489
     */
490
    public function saveEntity($entity, $unlock = true)
491
    {
492
        $companyFieldMatches = [];
493
        $fields              = $entity->getFields();
494
        $company             = null;
495
496
        //check to see if we can glean information from ip address
497
        if (!$entity->imported && count($ips = $entity->getIpAddresses())) {
498
            $details = $ips->first()->getIpDetails();
499
            // Only update with IP details if none of the following are set to prevent wrong combinations
500
            if (empty($fields['core']['city']['value']) && empty($fields['core']['state']['value']) && empty($fields['core']['country']['value']) && empty($fields['core']['zipcode']['value'])) {
501
                if ($this->coreParametersHelper->get('anonymize_ip') && $this->ipLookupHelper->getRealIp()) {
502
                    $details = $this->ipLookupHelper->getIpDetails($this->ipLookupHelper->getRealIp());
503
                }
504
505
                if (!empty($details['city'])) {
506
                    $entity->addUpdatedField('city', $details['city']);
507
                    $companyFieldMatches['city'] = $details['city'];
508
                }
509
510
                if (!empty($details['region'])) {
511
                    $entity->addUpdatedField('state', $details['region']);
512
                    $companyFieldMatches['state'] = $details['region'];
513
                }
514
515
                if (!empty($details['country'])) {
516
                    $entity->addUpdatedField('country', $details['country']);
517
                    $companyFieldMatches['country'] = $details['country'];
518
                }
519
520
                if (!empty($details['zipcode'])) {
521
                    $entity->addUpdatedField('zipcode', $details['zipcode']);
522
                }
523
            }
524
525
            if (!$entity->getCompany() && !empty($details['organization']) && $this->coreParametersHelper->get('ip_lookup_create_organization', false)) {
526
                $entity->addUpdatedField('company', $details['organization']);
527
            }
528
        }
529
530
        $updatedFields = $entity->getUpdatedFields();
531
        if (isset($updatedFields['company'])) {
532
            $companyFieldMatches['company']            = $updatedFields['company'];
533
            [$company, $leadAdded, $companyEntity]     = IdentifyCompanyHelper::identifyLeadsCompany($companyFieldMatches, $entity, $this->companyModel);
534
            if ($leadAdded) {
535
                $entity->addCompanyChangeLogEntry('form', 'Identify Company', 'Lead added to the company, '.$company['companyname'], $company['id']);
536
            }
537
        }
538
539
        $this->processManipulator($entity);
540
541
        $this->setEntityDefaultValues($entity);
542
543
        $this->ipAddressModel->saveIpAddressesReferencesForContact($entity);
544
545
        parent::saveEntity($entity, $unlock);
546
547
        if (!empty($company)) {
548
            // Save after the lead in for new leads created through the API and maybe other places
549
            $this->companyModel->addLeadToCompany($companyEntity, $entity);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $companyEntity does not seem to be defined for all execution paths leading up to this point.
Loading history...
550
            $this->setPrimaryCompany($companyEntity->getId(), $entity->getId());
551
        }
552
553
        $this->em->clear(CompanyChangeLog::class);
554
    }
555
556
    /**
557
     * @param object $entity
558
     */
559
    public function deleteEntity($entity)
560
    {
561
        // Delete custom avatar if one exists
562
        $imageDir = $this->pathsHelper->getSystemPath('images', true);
563
        $avatar   = $imageDir.'/lead_avatars/avatar'.$entity->getId();
564
565
        if (file_exists($avatar)) {
566
            unlink($avatar);
567
        }
568
569
        parent::deleteEntity($entity);
570
    }
571
572
    /**
573
     * Clear all Lead entities.
574
     */
575
    public function clearEntities()
576
    {
577
        $this->getRepository()->clear();
578
    }
579
580
    /**
581
     * Populates custom field values for updating the lead. Also retrieves social media data.
582
     *
583
     * @param bool|false $overwriteWithBlank
584
     * @param bool|true  $fetchSocialProfiles
585
     * @param bool|false $bindWithForm        Send $data through the Lead form and only use valid data (should be used with request data)
586
     *
587
     * @return array
588
     *
589
     * @throws ImportFailedException
590
     */
591
    public function setFieldValues(Lead $lead, array $data, $overwriteWithBlank = false, $fetchSocialProfiles = true, $bindWithForm = false)
592
    {
593
        if ($fetchSocialProfiles) {
594
            //@todo - add a catch to NOT do social gleaning if a lead is created via a form, etc as we do not want the user to experience the wait
595
            //generate the social cache
596
            [$socialCache, $socialFeatureSettings] = $this->integrationHelper->getUserProfiles(
597
                $lead,
598
                $data,
599
                true,
600
                null,
601
                false,
602
                true
603
            );
604
605
            //set the social cache while we have it
606
            if (!empty($socialCache)) {
607
                $lead->setSocialCache($socialCache);
608
            }
609
        }
610
611
        if (isset($data['stage'])) {
612
            $stagesChangeLogRepo  = $this->getStagesChangeLogRepository();
613
            $currentLeadStageId   = $stagesChangeLogRepo->getCurrentLeadStage($lead->getId());
614
            $currentLeadStageName = null;
615
            if ($currentLeadStageId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $currentLeadStageId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
616
                $currentStage = $this->em->getRepository(Stage::class)->findByIdOrName($currentLeadStageId);
0 ignored issues
show
Bug introduced by
The method findByIdOrName() does not exist on Doctrine\Common\Persistence\ObjectRepository. Did you maybe mean findBy()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

616
                $currentStage = $this->em->getRepository(Stage::class)->/** @scrutinizer ignore-call */ findByIdOrName($currentLeadStageId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
617
                if ($currentStage) {
618
                    $currentLeadStageName = $currentStage->getName();
619
                }
620
            }
621
622
            $newLeadStageIdOrName = is_object($data['stage']) ? $data['stage']->getId() : $data['stage'];
623
            if ((int) $newLeadStageIdOrName !== $currentLeadStageId && $newLeadStageIdOrName !== $currentLeadStageName) {
624
                $newStage = $this->em->getRepository(Stage::class)->findByIdOrName($newLeadStageIdOrName);
625
                if ($newStage) {
626
                    $lead->stageChangeLogEntry(
627
                        $newStage,
628
                        $newStage->getId().':'.$newStage->getName(),
629
                        $this->translator->trans('mautic.stage.event.changed')
630
                    );
631
                } else {
632
                    throw new ImportFailedException($this->translator->trans('mautic.lead.import.stage.not.exists', ['id' => $newLeadStageIdOrName]));
633
                }
634
            }
635
        }
636
637
        //save the field values
638
        $fieldValues = $lead->getFields();
639
640
        if (empty($fieldValues) || $bindWithForm) {
641
            // Lead is new or they haven't been populated so let's build the fields now
642
            if (empty($this->flattenedFields)) {
643
                $this->flattenedFields = $this->leadFieldModel->getEntities(
644
                    [
645
                        'filter'         => ['isPublished' => true, 'object' => 'lead'],
646
                        'hydration_mode' => 'HYDRATE_ARRAY',
647
                    ]
648
                );
649
                $this->fieldsByGroup = $this->organizeFieldsByGroup($this->flattenedFields);
650
            }
651
652
            if (empty($fieldValues)) {
653
                $fieldValues = $this->fieldsByGroup;
654
            }
655
        }
656
657
        if ($bindWithForm) {
658
            // Cleanup the field values
659
            $form = $this->createForm(
660
                new Lead(), // use empty lead to prevent binding errors
661
                $this->formFactory,
662
                null,
663
                ['fields' => $this->flattenedFields, 'csrf_protection' => false, 'allow_extra_fields' => true]
664
            );
665
666
            // Unset stage and owner from the form because it's already been handled
667
            unset($data['stage'], $data['owner'], $data['tags']);
668
            // Prepare special fields
669
            $this->prepareParametersFromRequest($form, $data, $lead, [], $this->fieldsByGroup);
670
            // Submit the data
671
            $form->submit($data);
672
673
            if ($form->getErrors()->count()) {
674
                $this->logger->addDebug('LEAD: form validation failed with an error of '.$form->getErrors());
675
            }
676
            foreach ($form as $field => $formField) {
677
                if (isset($data[$field])) {
678
                    if ($formField->getErrors()->count()) {
679
                        $this->logger->addDebug('LEAD: '.$field.' failed form validation with an error of '.$formField->getErrors());
680
                        // Don't save bad data
681
                        unset($data[$field]);
682
                    } else {
683
                        $data[$field] = $formField->getData();
684
                    }
685
                }
686
            }
687
        }
688
689
        //update existing values
690
        foreach ($fieldValues as $group => &$groupFields) {
691
            if ('all' === $group) {
692
                continue;
693
            }
694
695
            foreach ($groupFields as $alias => &$field) {
696
                if (!isset($field['value'])) {
697
                    $field['value'] = null;
698
                }
699
700
                // Only update fields that are part of the passed $data array
701
                if (array_key_exists($alias, $data)) {
702
                    if (!$bindWithForm) {
703
                        $this->cleanFields($data, $field);
704
                    }
705
                    $curValue = $field['value'];
706
                    $newValue = isset($data[$alias]) ? $data[$alias] : '';
707
708
                    if (is_array($newValue)) {
709
                        $newValue = implode('|', $newValue);
710
                    }
711
712
                    $isEmpty = (null === $newValue || '' === $newValue);
713
                    if ($curValue !== $newValue && (!$isEmpty || ($isEmpty && $overwriteWithBlank))) {
714
                        $field['value'] = $newValue;
715
                        $lead->addUpdatedField($alias, $newValue, $curValue);
716
                    }
717
718
                    //if empty, check for social media data to plug the hole
719
                    if (empty($newValue) && !empty($socialCache)) {
720
                        foreach ($socialCache as $service => $details) {
721
                            //check to see if a field has been assigned
722
723
                            if (!empty($socialFeatureSettings[$service]['leadFields'])
724
                                && in_array($field['alias'], $socialFeatureSettings[$service]['leadFields'])
725
                            ) {
726
                                //check to see if the data is available
727
                                $key = array_search($field['alias'], $socialFeatureSettings[$service]['leadFields']);
728
                                if (isset($details['profile'][$key])) {
729
                                    //Found!!
730
                                    $field['value'] = $details['profile'][$key];
731
                                    $lead->addUpdatedField($alias, $details['profile'][$key]);
732
                                    break;
733
                                }
734
                            }
735
                        }
736
                    }
737
                }
738
            }
739
        }
740
741
        $lead->setFields($fieldValues);
742
    }
743
744
    /**
745
     * Disassociates a user from leads.
746
     *
747
     * @param $userId
748
     */
749
    public function disassociateOwner($userId)
750
    {
751
        $leads = $this->getRepository()->findByOwner($userId);
0 ignored issues
show
Bug introduced by
The method findByOwner() does not exist on Mautic\LeadBundle\Entity\LeadRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

751
        $leads = $this->getRepository()->/** @scrutinizer ignore-call */ findByOwner($userId);
Loading history...
752
        foreach ($leads as $lead) {
753
            $lead->setOwner(null);
754
            $this->saveEntity($lead);
755
        }
756
    }
757
758
    /**
759
     * Get list of entities for autopopulate fields.
760
     *
761
     * @param $type
762
     * @param $filter
763
     * @param $limit
764
     * @param $start
765
     *
766
     * @return array
767
     */
768
    public function getLookupResults($type, $filter = '', $limit = 10, $start = 0)
769
    {
770
        $results = [];
771
        switch ($type) {
772
            case 'user':
773
                $results = $this->em->getRepository('MauticUserBundle:User')->getUserList($filter, $limit, $start, ['lead' => 'leads']);
774
                break;
775
        }
776
777
        return $results;
778
    }
779
780
    /**
781
     * Obtain an array of users for api lead edits.
782
     *
783
     * @return mixed
784
     */
785
    public function getOwnerList()
786
    {
787
        return $this->em->getRepository('MauticUserBundle:User')->getUserList('', 0);
788
    }
789
790
    /**
791
     * Obtains a list of leads based off IP.
792
     *
793
     * @param $ip
794
     *
795
     * @return mixed
796
     */
797
    public function getLeadsByIp($ip)
798
    {
799
        return $this->getRepository()->getLeadsByIp($ip);
800
    }
801
802
    /**
803
     * Obtains a list of leads based a list of IDs.
804
     *
805
     * @return Paginator
806
     */
807
    public function getLeadsByIds(array $ids)
808
    {
809
        return $this->getEntities([
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getEntitie...', 'value' => $ids))))) returns the type Mautic\LeadBundle\Entity\Lead[]|array which is incompatible with the documented return type Doctrine\ORM\Tools\Pagination\Paginator.
Loading history...
810
            'filter' => [
811
                'force' => [
812
                    [
813
                        'column' => 'l.id',
814
                        'expr'   => 'in',
815
                        'value'  => $ids,
816
                    ],
817
                ],
818
            ],
819
        ]);
820
    }
821
822
    /**
823
     * @return bool
824
     */
825
    public function canEditContact(Lead $contact)
826
    {
827
        return $this->security->hasEntityAccess('lead:leads:editown', 'lead:leads:editother', $contact->getPermissionUser());
828
    }
829
830
    /**
831
     * Gets the details of a lead if not already set.
832
     *
833
     * @param $lead
834
     *
835
     * @return mixed
836
     */
837
    public function getLeadDetails($lead)
838
    {
839
        if ($lead instanceof Lead) {
840
            $fields = $lead->getFields();
841
            if (!empty($fields)) {
842
                return $fields;
843
            }
844
        }
845
846
        $leadId = ($lead instanceof Lead) ? $lead->getId() : (int) $lead;
847
848
        return $this->getRepository()->getFieldValues($leadId);
849
    }
850
851
    /**
852
     * Reorganizes a field list to be keyed by field's group then alias.
853
     *
854
     * @param $fields
855
     *
856
     * @return array
857
     */
858
    public function organizeFieldsByGroup($fields)
859
    {
860
        $array = [];
861
862
        foreach ($fields as $field) {
863
            if ($field instanceof LeadField) {
864
                $alias = $field->getAlias();
865
                if ($field->isPublished() and 'Lead' === $field->getObject()) {
866
                    $group                                = $field->getGroup();
867
                    $array[$group][$alias]['id']          = $field->getId();
868
                    $array[$group][$alias]['group']       = $group;
869
                    $array[$group][$alias]['label']       = $field->getLabel();
870
                    $array[$group][$alias]['alias']       = $alias;
871
                    $array[$group][$alias]['type']        = $field->getType();
872
                    $array[$group][$alias]['properties']  = $field->getProperties();
873
                }
874
            } else {
875
                $alias = $field['alias'];
876
                if ($field['isPublished'] and 'lead' === $field['object']) {
877
                    $group                                = $field['group'];
878
                    $array[$group][$alias]['id']          = $field['id'];
879
                    $array[$group][$alias]['group']       = $group;
880
                    $array[$group][$alias]['label']       = $field['label'];
881
                    $array[$group][$alias]['alias']       = $alias;
882
                    $array[$group][$alias]['type']        = $field['type'];
883
                    $array[$group][$alias]['properties']  = $field['properties'] ?? [];
884
                }
885
            }
886
        }
887
888
        //make sure each group key is present
889
        $groups = ['core', 'social', 'personal', 'professional'];
890
        foreach ($groups as $g) {
891
            if (!isset($array[$g])) {
892
                $array[$g] = [];
893
            }
894
        }
895
896
        return $array;
897
    }
898
899
    /**
900
     * Returns flat array for single lead.
901
     *
902
     * @param $leadId
903
     *
904
     * @return array
905
     */
906
    public function getLead($leadId)
907
    {
908
        return $this->getRepository()->getLead($leadId);
909
    }
910
911
    /**
912
     * Get the contat from request (ct/clickthrough) and handles auto merging of contact data from request parameters.
913
     *
914
     * @param array $queryFields
915
     *
916
     * @return array|Lead|null
917
     */
918
    public function getContactFromRequest($queryFields = [])
919
    {
920
        // @todo Instantiate here until we can remove circular dependency on LeadModel in order to make it a service
921
        $requestStack = new RequestStack();
922
        $requestStack->push($this->request);
0 ignored issues
show
Bug introduced by
It seems like $this->request can also be of type null; however, parameter $request of Symfony\Component\HttpFo...on\RequestStack::push() does only seem to accept Symfony\Component\HttpFoundation\Request, 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 ignore-type  annotation

922
        $requestStack->push(/** @scrutinizer ignore-type */ $this->request);
Loading history...
923
        $contactRequestHelper = new ContactRequestHelper(
924
            $this,
925
            $this->contactTracker,
926
            $this->coreParametersHelper,
927
            $this->ipLookupHelper,
928
            $requestStack,
929
            $this->logger,
930
            $this->dispatcher
931
        );
932
933
        return $contactRequestHelper->getContactFromQuery($queryFields);
934
    }
935
936
    /**
937
     * @param bool $returnWithQueryFields
938
     *
939
     * @return array|Lead
940
     */
941
    public function checkForDuplicateContact(array $queryFields, Lead $lead = null, $returnWithQueryFields = false, $onlyPubliclyUpdateable = false)
942
    {
943
        // Search for lead by request and/or update lead fields if some data were sent in the URL query
944
        if (empty($this->availableLeadFields)) {
945
            $filter = ['isPublished' => true, 'object' => 'lead'];
946
947
            if ($onlyPubliclyUpdateable) {
948
                $filter['isPubliclyUpdatable'] = true;
949
            }
950
951
            $this->availableLeadFields = $this->leadFieldModel->getFieldList(
952
                false,
953
                false,
954
                $filter
955
            );
956
        }
957
958
        if (is_null($lead)) {
959
            $lead = new Lead();
960
        }
961
962
        $uniqueFields    = $this->leadFieldModel->getUniqueIdentifierFields();
963
        $uniqueFieldData = [];
964
        $inQuery         = array_intersect_key($queryFields, $this->availableLeadFields);
965
        $values          = $onlyPubliclyUpdateable ? $inQuery : $queryFields;
966
967
        // Run values through setFieldValues to clean them first
968
        $this->setFieldValues($lead, $values, false, false);
969
        $cleanFields = $lead->getFields();
970
971
        foreach ($inQuery as $k => $v) {
972
            if (empty($queryFields[$k])) {
973
                unset($inQuery[$k]);
974
            }
975
        }
976
977
        foreach ($cleanFields as $group) {
978
            foreach ($group as $key => $field) {
979
                if (array_key_exists($key, $uniqueFields) && !empty($field['value'])) {
980
                    $uniqueFieldData[$key] = $field['value'];
981
                }
982
            }
983
        }
984
985
        // Check for leads using unique identifier
986
        if (count($uniqueFieldData)) {
987
            $existingLeads = $this->getRepository()->getLeadsByUniqueFields($uniqueFieldData, ($lead) ? $lead->getId() : null);
0 ignored issues
show
introduced by
$lead is of type Mautic\LeadBundle\Entity\Lead, thus it always evaluated to true.
Loading history...
988
989
            if (!empty($existingLeads)) {
990
                $this->logger->addDebug("LEAD: Existing contact ID# {$existingLeads[0]->getId()} found through query identifiers.");
991
                // Merge with existing lead or use the one found
992
                $lead = ($lead->getId()) ? $this->mergeLeads($lead, $existingLeads[0]) : $existingLeads[0];
0 ignored issues
show
Deprecated Code introduced by
The function Mautic\LeadBundle\Model\LeadModel::mergeLeads() has been deprecated: 2.13.0; to be removed in 3.0. Use \Mautic\LeadBundle\Deduplicate\ContactMerger instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

992
                $lead = ($lead->getId()) ? /** @scrutinizer ignore-deprecated */ $this->mergeLeads($lead, $existingLeads[0]) : $existingLeads[0];

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
993
            }
994
        }
995
996
        return $returnWithQueryFields ? [$lead, $inQuery] : $lead;
997
    }
998
999
    /**
1000
     * Get a list of segments this lead belongs to.
1001
     *
1002
     * @param bool $forLists
1003
     * @param bool $arrayHydration
1004
     * @param bool $isPublic
1005
     *
1006
     * @return mixed
1007
     */
1008
    public function getLists(Lead $lead, $forLists = false, $arrayHydration = false, $isPublic = false, $isPreferenceCenter = false)
1009
    {
1010
        $repo = $this->em->getRepository(LeadList::class);
1011
1012
        return $repo->getLeadLists($lead->getId(), $forLists, $arrayHydration, $isPublic, $isPreferenceCenter);
1013
    }
1014
1015
    /**
1016
     * Get a list of companies this contact belongs to.
1017
     *
1018
     * @return mixed
1019
     */
1020
    public function getCompanies(Lead $lead)
1021
    {
1022
        $repo = $this->em->getRepository('MauticLeadBundle:CompanyLead');
1023
1024
        return $repo->getCompaniesByLeadId($lead->getId());
1025
    }
1026
1027
    /**
1028
     * Add lead to lists.
1029
     *
1030
     * @param array|Lead     $lead
1031
     * @param array|LeadList $lists
1032
     * @param bool           $manuallyAdded
1033
     */
1034
    public function addToLists($lead, $lists, $manuallyAdded = true)
1035
    {
1036
        $this->leadListModel->addLead($lead, $lists, $manuallyAdded);
1037
    }
1038
1039
    /**
1040
     * Remove lead from lists.
1041
     *
1042
     * @param      $lead
1043
     * @param      $lists
1044
     * @param bool $manuallyRemoved
1045
     */
1046
    public function removeFromLists($lead, $lists, $manuallyRemoved = true)
1047
    {
1048
        $this->leadListModel->removeLead($lead, $lists, $manuallyRemoved);
1049
    }
1050
1051
    /**
1052
     * Add lead to Stage.
1053
     *
1054
     * @param array|Lead  $lead
1055
     * @param array|Stage $stage
1056
     * @param bool        $manuallyAdded
1057
     *
1058
     * @return $this
1059
     */
1060
    public function addToStages($lead, $stage, $manuallyAdded = true)
1061
    {
1062
        if (!$lead instanceof Lead) {
1063
            $leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
1064
            $lead   = $this->em->getReference('MauticLeadBundle:Lead', $leadId);
1065
        }
1066
        $lead->setStage($stage);
0 ignored issues
show
Bug introduced by
It seems like $stage can also be of type array; however, parameter $stage of Mautic\LeadBundle\Entity\Lead::setStage() does only seem to accept Mautic\StageBundle\Entity\Stage|null, 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 ignore-type  annotation

1066
        $lead->setStage(/** @scrutinizer ignore-type */ $stage);
Loading history...
1067
        $lead->stageChangeLogEntry(
1068
            $stage,
1069
            $stage->getId().': '.$stage->getName(),
1070
            $this->translator->trans('mautic.stage.event.added.batch')
1071
        );
1072
1073
        return $this;
1074
    }
1075
1076
    /**
1077
     * Remove lead from Stage.
1078
     *
1079
     * @param      $lead
1080
     * @param      $stage
1081
     * @param bool $manuallyRemoved
1082
     *
1083
     * @return $this
1084
     */
1085
    public function removeFromStages($lead, $stage, $manuallyRemoved = true)
1086
    {
1087
        $lead->setStage(null);
1088
        $lead->stageChangeLogEntry(
1089
            $stage,
1090
            $stage->getId().': '.$stage->getName(),
1091
            $this->translator->trans('mautic.stage.event.removed.batch')
1092
        );
1093
1094
        return $this;
1095
    }
1096
1097
    /**
1098
     * @param string $channel
1099
     *
1100
     * @return mixed
1101
     */
1102
    public function getFrequencyRules(Lead $lead, $channel = null)
1103
    {
1104
        if (is_array($channel)) {
0 ignored issues
show
introduced by
The condition is_array($channel) is always false.
Loading history...
1105
            $channel = key($channel);
1106
        }
1107
1108
        /** @var \Mautic\LeadBundle\Entity\FrequencyRuleRepository $frequencyRuleRepo */
1109
        $frequencyRuleRepo = $this->em->getRepository('MauticLeadBundle:FrequencyRule');
1110
        $frequencyRules    = $frequencyRuleRepo->getFrequencyRules($channel, $lead->getId());
0 ignored issues
show
Bug introduced by
It seems like $channel can also be of type string; however, parameter $channel of Mautic\LeadBundle\Entity...ry::getFrequencyRules() does only seem to accept null, 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 ignore-type  annotation

1110
        $frequencyRules    = $frequencyRuleRepo->getFrequencyRules(/** @scrutinizer ignore-type */ $channel, $lead->getId());
Loading history...
1111
1112
        if (empty($frequencyRules)) {
1113
            return [];
1114
        }
1115
1116
        return $frequencyRules;
1117
    }
1118
1119
    /**
1120
     * Set frequency rules for lead per channel.
1121
     *
1122
     * @param null $data
1123
     * @param null $leadLists
1124
     *
1125
     * @return bool Returns true
1126
     */
1127
    public function setFrequencyRules(Lead $lead, $data = null, $leadLists = null, $persist = true)
1128
    {
1129
        // One query to get all the lead's current frequency rules and go ahead and create entities for them
1130
        $frequencyRules = $lead->getFrequencyRules()->toArray();
1131
        $entities       = [];
1132
        $channels       = $this->getPreferenceChannels();
1133
1134
        foreach ($channels as $ch) {
1135
            if (empty($data['lead_channels']['preferred_channel'])) {
1136
                $data['lead_channels']['preferred_channel'] = $ch;
1137
            }
1138
1139
            $frequencyRule = (isset($frequencyRules[$ch])) ? $frequencyRules[$ch] : new FrequencyRule();
1140
            $frequencyRule->setChannel($ch);
1141
            $frequencyRule->setLead($lead);
1142
            $frequencyRule->setDateAdded(new \DateTime());
1143
1144
            if (!empty($data['lead_channels']['frequency_number_'.$ch]) && !empty($data['lead_channels']['frequency_time_'.$ch])) {
1145
                $frequencyRule->setFrequencyNumber($data['lead_channels']['frequency_number_'.$ch]);
1146
                $frequencyRule->setFrequencyTime($data['lead_channels']['frequency_time_'.$ch]);
1147
            } else {
1148
                $frequencyRule->setFrequencyNumber(null);
1149
                $frequencyRule->setFrequencyTime(null);
1150
            }
1151
1152
            $frequencyRule->setPauseFromDate(!empty($data['lead_channels']['contact_pause_start_date_'.$ch]) ? $data['lead_channels']['contact_pause_start_date_'.$ch] : null);
1153
            $frequencyRule->setPauseToDate(!empty($data['lead_channels']['contact_pause_end_date_'.$ch]) ? $data['lead_channels']['contact_pause_end_date_'.$ch] : null);
1154
1155
            $frequencyRule->setLead($lead);
1156
            $frequencyRule->setPreferredChannel($data['lead_channels']['preferred_channel'] === $ch);
1157
1158
            if ($persist) {
1159
                $entities[$ch] = $frequencyRule;
1160
            } else {
1161
                $lead->addFrequencyRule($frequencyRule);
1162
            }
1163
        }
1164
1165
        if (!empty($entities)) {
1166
            $this->em->getRepository('MauticLeadBundle:FrequencyRule')->saveEntities($entities);
1167
        }
1168
1169
        foreach ($data['lead_lists'] as $leadList) {
1170
            if (!isset($leadLists[$leadList])) {
1171
                $this->addToLists($lead, [$leadList]);
1172
            }
1173
        }
1174
        // Delete lists that were removed
1175
        $deletedLists = array_diff(array_keys($leadLists), $data['lead_lists']);
1176
        if (!empty($deletedLists)) {
1177
            $this->removeFromLists($lead, $deletedLists);
1178
        }
1179
1180
        if (!empty($data['global_categories'])) {
1181
            $this->addToCategory($lead, $data['global_categories']);
1182
        }
1183
        $leadCategories = $this->getLeadCategories($lead);
1184
        // Delete categories that were removed
1185
        $deletedCategories = array_diff($leadCategories, $data['global_categories']);
1186
1187
        if (!empty($deletedCategories)) {
1188
            $this->removeFromCategories($deletedCategories);
1189
        }
1190
1191
        // Delete channels that were removed
1192
        $deleted = array_diff_key($frequencyRules, $entities);
1193
        if (!empty($deleted)) {
1194
            $this->em->getRepository('MauticLeadBundle:FrequencyRule')->deleteEntities($deleted);
1195
        }
1196
1197
        return true;
1198
    }
1199
1200
    /**
1201
     * @param $categories
1202
     * @param bool $manuallyAdded
1203
     *
1204
     * @return array
1205
     */
1206
    public function addToCategory(Lead $lead, $categories, $manuallyAdded = true)
1207
    {
1208
        $leadCategories = $this->getLeadCategoryRepository()->getLeadCategories($lead);
1209
1210
        $results = [];
1211
        foreach ($categories as $category) {
1212
            if (!isset($leadCategories[$category])) {
1213
                $newLeadCategory = new LeadCategory();
1214
                $newLeadCategory->setLead($lead);
1215
                if (!$category instanceof Category) {
1216
                    $category = $this->categoryModel->getEntity($category);
1217
                }
1218
                $newLeadCategory->setCategory($category);
1219
                $newLeadCategory->setDateAdded(new \DateTime());
1220
                $newLeadCategory->setManuallyAdded($manuallyAdded);
1221
                $results[$category->getId()] = $newLeadCategory;
1222
1223
                if ($this->dispatcher->hasListeners(LeadEvents::LEAD_CATEGORY_CHANGE)) {
1224
                    $this->dispatcher->dispatch(LeadEvents::LEAD_CATEGORY_CHANGE, new CategoryChangeEvent($lead, $category));
1225
                }
1226
            }
1227
        }
1228
        if (!empty($results)) {
1229
            $this->getLeadCategoryRepository()->saveEntities($results);
1230
        }
1231
1232
        return $results;
1233
    }
1234
1235
    /**
1236
     * @param $categories
1237
     */
1238
    public function removeFromCategories($categories)
1239
    {
1240
        $deleteCats = [];
1241
        if (is_array($categories)) {
1242
            foreach ($categories as $key => $category) {
1243
                /** @var LeadCategory $category */
1244
                $category     = $this->getLeadCategoryRepository()->getEntity($key);
1245
                $deleteCats[] = $category;
1246
1247
                if ($this->dispatcher->hasListeners(LeadEvents::LEAD_CATEGORY_CHANGE)) {
1248
                    $this->dispatcher->dispatch(LeadEvents::LEAD_CATEGORY_CHANGE, new CategoryChangeEvent($category->getLead(), $category->getCategory(), false));
1249
                }
1250
            }
1251
        } elseif ($categories instanceof LeadCategory) {
1252
            $deleteCats[] = $categories;
1253
1254
            if ($this->dispatcher->hasListeners(LeadEvents::LEAD_CATEGORY_CHANGE)) {
1255
                $this->dispatcher->dispatch(LeadEvents::LEAD_CATEGORY_CHANGE, new CategoryChangeEvent($categories->getLead(), $categories->getCategory(), false));
1256
            }
1257
        }
1258
1259
        if (!empty($deleteCats)) {
1260
            $this->getLeadCategoryRepository()->deleteEntities($deleteCats);
1261
        }
1262
    }
1263
1264
    /**
1265
     * @return array
1266
     */
1267
    public function getLeadCategories(Lead $lead)
1268
    {
1269
        $leadCategories   = $this->getLeadCategoryRepository()->getLeadCategories($lead);
1270
        $leadCategoryList = [];
1271
        foreach ($leadCategories as $category) {
1272
            $leadCategoryList[$category['id']] = $category['category_id'];
1273
        }
1274
1275
        return $leadCategoryList;
1276
    }
1277
1278
    /**
1279
     * @param array        $fields
1280
     * @param array        $data
1281
     * @param null         $owner
1282
     * @param null         $list
1283
     * @param null         $tags
1284
     * @param bool         $persist
1285
     * @param LeadEventLog $eventLog
1286
     *
1287
     * @return bool|null
1288
     *
1289
     * @throws \Exception
1290
     */
1291
    public function import($fields, $data, $owner = null, $list = null, $tags = null, $persist = true, LeadEventLog $eventLog = null, $importId = null)
1292
    {
1293
        $fields    = array_flip($fields);
1294
        $fieldData = [];
1295
1296
        // Extract company data and import separately
1297
        // Modifies the data array
1298
        $company                           = null;
1299
        [$companyFields, $companyData]     = $this->companyModel->extractCompanyDataFromImport($fields, $data);
1300
1301
        if (!empty($companyData)) {
1302
            $companyFields = array_flip($companyFields);
1303
            $this->companyModel->import($companyFields, $companyData, $owner, $list, $tags, $persist, $eventLog);
1304
            $companyFields = array_flip($companyFields);
1305
1306
            $companyName    = isset($companyFields['companyname']) ? $companyData[$companyFields['companyname']] : null;
1307
            $companyCity    = isset($companyFields['companycity']) ? $companyData[$companyFields['companycity']] : null;
1308
            $companyCountry = isset($companyFields['companycountry']) ? $companyData[$companyFields['companycountry']] : null;
1309
            $companyState   = isset($companyFields['companystate']) ? $companyData[$companyFields['companystate']] : null;
1310
1311
            $company = $this->companyModel->getRepository()->identifyCompany($companyName, $companyCity, $companyCountry, $companyState);
1312
        }
1313
1314
        foreach ($fields as $leadField => $importField) {
1315
            // Prevent overwriting existing data with empty data
1316
            if (array_key_exists($importField, $data) && !is_null($data[$importField]) && '' != $data[$importField]) {
1317
                $fieldData[$leadField] = InputHelper::_($data[$importField], 'string');
1318
            }
1319
        }
1320
1321
        $lead   = $this->checkForDuplicateContact($fieldData);
1322
        $merged = ($lead->getId());
1323
1324
        if (!empty($fields['dateAdded']) && !empty($data[$fields['dateAdded']])) {
1325
            $dateAdded = new DateTimeHelper($data[$fields['dateAdded']]);
1326
            $lead->setDateAdded($dateAdded->getUtcDateTime());
1327
        }
1328
        unset($fieldData['dateAdded']);
1329
1330
        if (!empty($fields['dateModified']) && !empty($data[$fields['dateModified']])) {
1331
            $dateModified = new DateTimeHelper($data[$fields['dateModified']]);
1332
            $lead->setDateModified($dateModified->getUtcDateTime());
1333
        }
1334
        unset($fieldData['dateModified']);
1335
1336
        if (!empty($fields['lastActive']) && !empty($data[$fields['lastActive']])) {
1337
            $lastActive = new DateTimeHelper($data[$fields['lastActive']]);
1338
            $lead->setLastActive($lastActive->getUtcDateTime());
1339
        }
1340
        unset($fieldData['lastActive']);
1341
1342
        if (!empty($fields['dateIdentified']) && !empty($data[$fields['dateIdentified']])) {
1343
            $dateIdentified = new DateTimeHelper($data[$fields['dateIdentified']]);
1344
            $lead->setDateIdentified($dateIdentified->getUtcDateTime());
1345
        }
1346
        unset($fieldData['dateIdentified']);
1347
1348
        if (!empty($fields['createdByUser']) && !empty($data[$fields['createdByUser']])) {
1349
            $userRepo      = $this->em->getRepository('MauticUserBundle:User');
1350
            $createdByUser = $userRepo->findByIdentifier($data[$fields['createdByUser']]);
0 ignored issues
show
Bug introduced by
The method findByIdentifier() does not exist on Doctrine\Common\Persistence\ObjectRepository. Did you maybe mean findBy()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1350
            /** @scrutinizer ignore-call */ 
1351
            $createdByUser = $userRepo->findByIdentifier($data[$fields['createdByUser']]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1351
            if (null !== $createdByUser) {
1352
                $lead->setCreatedBy($createdByUser);
1353
            }
1354
        }
1355
        unset($fieldData['createdByUser']);
1356
1357
        if (!empty($fields['modifiedByUser']) && !empty($data[$fields['modifiedByUser']])) {
1358
            $userRepo       = $this->em->getRepository('MauticUserBundle:User');
1359
            $modifiedByUser = $userRepo->findByIdentifier($data[$fields['modifiedByUser']]);
1360
            if (null !== $modifiedByUser) {
1361
                $lead->setModifiedBy($modifiedByUser);
1362
            }
1363
        }
1364
        unset($fieldData['modifiedByUser']);
1365
1366
        if (!empty($fields['ip']) && !empty($data[$fields['ip']])) {
1367
            $addresses = explode(',', $data[$fields['ip']]);
1368
            foreach ($addresses as $address) {
1369
                $address = trim($address);
1370
                if (!$ipAddress = $this->ipAddressModel->findOneByIpAddress($address)) {
1371
                    $ipAddress = new IpAddress();
1372
                    $ipAddress->setIpAddress($address);
1373
                }
1374
                $lead->addIpAddress($ipAddress);
1375
            }
1376
        }
1377
        unset($fieldData['ip']);
1378
1379
        if (!empty($fields['points']) && !empty($data[$fields['points']]) && null === $lead->getId()) {
0 ignored issues
show
introduced by
The condition null === $lead->getId() is always false.
Loading history...
1380
            // Add points only for new leads
1381
            $lead->setPoints($data[$fields['points']]);
1382
1383
            //add a lead point change log
1384
            $log = new PointsChangeLog();
1385
            $log->setDelta($data[$fields['points']]);
1386
            $log->setLead($lead);
1387
            $log->setType('lead');
1388
            $log->setEventName($this->translator->trans('mautic.lead.import.event.name'));
1389
            $log->setActionName($this->translator->trans('mautic.lead.import.action.name', [
1390
                '%name%' => $this->userHelper->getUser()->getUsername(),
1391
            ]));
1392
            $log->setIpAddress($this->ipLookupHelper->getIpAddress());
1393
            $log->setDateAdded(new \DateTime());
1394
            $lead->addPointsChangeLog($log);
1395
        }
1396
1397
        if (!empty($fields['stage']) && !empty($data[$fields['stage']])) {
1398
            static $stages = [];
1399
            $stageName     = $data[$fields['stage']];
1400
            if (!array_key_exists($stageName, $stages)) {
1401
                // Set stage for contact
1402
                $stage = $this->em->getRepository('MauticStageBundle:Stage')->getStageByName($stageName);
1403
1404
                if (empty($stage)) {
1405
                    $stage = new Stage();
1406
                    $stage->setName($stageName);
1407
                    $stages[$stageName] = $stage;
1408
                }
1409
            } else {
1410
                $stage = $stages[$stageName];
1411
            }
1412
1413
            $lead->setStage($stage);
1414
1415
            //add a contact stage change log
1416
            $log = new StagesChangeLog();
1417
            $log->setStage($stage);
1418
            $log->setEventName($stage->getId().':'.$stage->getName());
1419
            $log->setLead($lead);
0 ignored issues
show
Bug introduced by
It seems like $lead can also be of type array; however, parameter $lead of Mautic\LeadBundle\Entity...gesChangeLog::setLead() does only seem to accept Mautic\LeadBundle\Entity\Lead, 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 ignore-type  annotation

1419
            $log->setLead(/** @scrutinizer ignore-type */ $lead);
Loading history...
1420
            $log->setActionName(
1421
                $this->translator->trans(
1422
                    'mautic.stage.import.action.name',
1423
                    [
1424
                        '%name%' => $this->userHelper->getUser()->getUsername(),
1425
                    ]
1426
                )
1427
            );
1428
            $log->setDateAdded(new \DateTime());
1429
            $lead->stageChangeLog($log);
1430
        }
1431
        unset($fieldData['stage']);
1432
1433
        // Set unsubscribe status
1434
        if (!empty($fields['doNotEmail']) && isset($data[$fields['doNotEmail']]) && (!empty($fields['email']) && !empty($data[$fields['email']]))) {
1435
            $doNotEmail = filter_var($data[$fields['doNotEmail']], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
1436
            if (null !== $doNotEmail) {
1437
                $reason = $this->translator->trans('mautic.lead.import.by.user', [
1438
                    '%user%' => $this->userHelper->getUser()->getUsername(),
1439
                ]);
1440
1441
                // The email must be set for successful unsubscribtion
1442
                $lead->addUpdatedField('email', $data[$fields['email']]);
1443
                if ($doNotEmail) {
1444
                    $event = new DoNotContactAddEvent($lead, 'email', $reason, DNC::MANUAL);
0 ignored issues
show
Bug introduced by
It seems like $lead can also be of type array; however, parameter $lead of Mautic\LeadBundle\Event\...AddEvent::__construct() does only seem to accept Mautic\LeadBundle\Entity\Lead, 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 ignore-type  annotation

1444
                    $event = new DoNotContactAddEvent(/** @scrutinizer ignore-type */ $lead, 'email', $reason, DNC::MANUAL);
Loading history...
1445
                    $this->dispatcher->dispatch(DoNotContactAddEvent::ADD_DONOT_CONTACT, $event);
1446
                } else {
1447
                    $event = new DoNotContactRemoveEvent($lead, 'email');
0 ignored issues
show
Bug introduced by
It seems like $lead can also be of type array; however, parameter $lead of Mautic\LeadBundle\Event\...oveEvent::__construct() does only seem to accept Mautic\LeadBundle\Entity\Lead, 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 ignore-type  annotation

1447
                    $event = new DoNotContactRemoveEvent(/** @scrutinizer ignore-type */ $lead, 'email');
Loading history...
1448
                    $this->dispatcher->dispatch(DoNotContactRemoveEvent::REMOVE_DONOT_CONTACT, $event);
1449
                }
1450
            }
1451
        }
1452
1453
        unset($fieldData['doNotEmail']);
1454
1455
        if (!empty($fields['ownerusername']) && !empty($data[$fields['ownerusername']])) {
1456
            try {
1457
                $newOwner = $this->userProvider->loadUserByUsername($data[$fields['ownerusername']]);
1458
                $lead->setOwner($newOwner);
1459
                //reset default import owner if exists owner for contact
1460
                $owner = null;
1461
            } catch (NonUniqueResultException $exception) {
1462
                // user not found
1463
            }
1464
        }
1465
        unset($fieldData['ownerusername']);
1466
1467
        if (null !== $owner) {
0 ignored issues
show
introduced by
The condition null !== $owner is always false.
Loading history...
1468
            $lead->setOwner($this->em->getReference('MauticUserBundle:User', $owner));
1469
        }
1470
1471
        if (null !== $tags) {
0 ignored issues
show
introduced by
The condition null !== $tags is always false.
Loading history...
1472
            $this->modifyTags($lead, $tags, null, false);
1473
        }
1474
1475
        if (empty($this->leadFields)) {
1476
            $this->leadFields = $this->leadFieldModel->getEntities(
1477
                [
1478
                    'filter' => [
1479
                        'force' => [
1480
                            [
1481
                                'column' => 'f.isPublished',
1482
                                'expr'   => 'eq',
1483
                                'value'  => true,
1484
                            ],
1485
                            [
1486
                                'column' => 'f.object',
1487
                                'expr'   => 'eq',
1488
                                'value'  => 'lead',
1489
                            ],
1490
                        ],
1491
                    ],
1492
                    'hydration_mode' => 'HYDRATE_ARRAY',
1493
                ]
1494
            );
1495
        }
1496
1497
        $fieldErrors = [];
1498
1499
        foreach ($this->leadFields as $leadField) {
1500
            if (isset($fieldData[$leadField['alias']])) {
1501
                if ('NULL' === $fieldData[$leadField['alias']]) {
1502
                    $fieldData[$leadField['alias']] = null;
1503
1504
                    continue;
1505
                }
1506
1507
                try {
1508
                    $this->cleanFields($fieldData, $leadField);
1509
                } catch (\Exception $exception) {
1510
                    $fieldErrors[] = $leadField['alias'].': '.$exception->getMessage();
1511
                }
1512
1513
                if ('email' === $leadField['type'] && !empty($fieldData[$leadField['alias']])) {
1514
                    try {
1515
                        $this->emailValidator->validate($fieldData[$leadField['alias']], false);
1516
                    } catch (\Exception $exception) {
1517
                        $fieldErrors[] = $leadField['alias'].': '.$exception->getMessage();
1518
                    }
1519
                }
1520
1521
                // Skip if the value is in the CSV row
1522
                continue;
1523
            } elseif ($lead->isNew() && $leadField['defaultValue']) {
1524
                // Fill in the default value if any
1525
                $fieldData[$leadField['alias']] = ('multiselect' === $leadField['type']) ? [$leadField['defaultValue']] : $leadField['defaultValue'];
1526
            }
1527
        }
1528
1529
        if ($fieldErrors) {
1530
            $fieldErrors = implode("\n", $fieldErrors);
1531
1532
            throw new \Exception($fieldErrors);
1533
        }
1534
1535
        // All clear
1536
        foreach ($fieldData as $field => $value) {
1537
            $lead->addUpdatedField($field, $value);
1538
        }
1539
1540
        $lead->imported = true;
1541
1542
        if ($eventLog) {
1543
            $action = $merged ? 'updated' : 'inserted';
1544
            $eventLog->setAction($action);
1545
        }
1546
1547
        if ($persist) {
1548
            $lead->setManipulator(new LeadManipulator(
1549
                'lead',
1550
                'import',
1551
                $importId,
1552
                $this->userHelper->getUser()->getName()
1553
            ));
1554
            $this->saveEntity($lead);
0 ignored issues
show
Bug introduced by
It seems like $lead can also be of type array; however, parameter $entity of Mautic\LeadBundle\Model\LeadModel::saveEntity() does only seem to accept Mautic\LeadBundle\Entity\Lead, 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 ignore-type  annotation

1554
            $this->saveEntity(/** @scrutinizer ignore-type */ $lead);
Loading history...
1555
1556
            if (null !== $list) {
0 ignored issues
show
introduced by
The condition null !== $list is always false.
Loading history...
1557
                $this->addToLists($lead, [$list]);
1558
            }
1559
1560
            if (null !== $company) {
1561
                $this->companyModel->addLeadToCompany($company, $lead);
1562
            }
1563
1564
            if ($eventLog) {
1565
                $lead->addEventLog($eventLog);
1566
            }
1567
        }
1568
1569
        return $merged;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $merged returns the type integer which is incompatible with the documented return type boolean|null.
Loading history...
1570
    }
1571
1572
    /**
1573
     * Update a leads tags.
1574
     *
1575
     * @param bool|false $removeOrphans
1576
     */
1577
    public function setTags(Lead $lead, array $tags, $removeOrphans = false)
1578
    {
1579
        /** @var Tag[] $currentTags */
1580
        $currentTags  = $lead->getTags();
1581
        $leadModified = $tagsDeleted = false;
1582
1583
        foreach ($currentTags as $tag) {
1584
            if (!in_array($tag->getId(), $tags)) {
1585
                // Tag has been removed
1586
                $lead->removeTag($tag);
1587
                $leadModified = $tagsDeleted = true;
1588
            } else {
1589
                // Remove tag so that what's left are new tags
1590
                $key = array_search($tag->getId(), $tags);
1591
                unset($tags[$key]);
1592
            }
1593
        }
1594
1595
        if (!empty($tags)) {
1596
            foreach ($tags as $tag) {
1597
                if (is_numeric($tag)) {
1598
                    // Existing tag being added to this lead
1599
                    $lead->addTag(
1600
                        $this->em->getReference('MauticLeadBundle:Tag', $tag)
0 ignored issues
show
Bug introduced by
It seems like $this->em->getReference(...cLeadBundle:Tag', $tag) can also be of type null; however, parameter $tag of Mautic\LeadBundle\Entity\Lead::addTag() does only seem to accept Mautic\LeadBundle\Entity\Tag, 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 ignore-type  annotation

1600
                        /** @scrutinizer ignore-type */ $this->em->getReference('MauticLeadBundle:Tag', $tag)
Loading history...
1601
                    );
1602
                } else {
1603
                    $lead->addTag(
1604
                        $this->getTagRepository()->getTagByNameOrCreateNewOne($tag)
1605
                    );
1606
                }
1607
            }
1608
            $leadModified = true;
1609
        }
1610
1611
        if ($leadModified) {
1612
            $this->saveEntity($lead);
1613
1614
            // Delete orphaned tags
1615
            if ($tagsDeleted && $removeOrphans) {
1616
                $this->getTagRepository()->deleteOrphans();
1617
            }
1618
        }
1619
    }
1620
1621
    /**
1622
     * Update a leads UTM tags.
1623
     */
1624
    public function setUtmTags(Lead $lead, UtmTag $utmTags)
1625
    {
1626
        $lead->setUtmTags($utmTags);
1627
1628
        $this->saveEntity($lead);
1629
    }
1630
1631
    /**
1632
     * Add leads UTM tags via API.
1633
     *
1634
     * @param array $params
1635
     */
1636
    public function addUTMTags(Lead $lead, $params)
1637
    {
1638
        // known "synonym" fields expected
1639
        $synonyms = ['useragent'  => 'user_agent',
1640
                     'remotehost' => 'remote_host', ];
1641
1642
        // convert 'query' option to an array if necessary
1643
        if (isset($params['query']) && !is_array($params['query'])) {
1644
            // assume it's a query string; convert it to array
1645
            parse_str($params['query'], $queryResult);
1646
            if (!empty($queryResult)) {
1647
                $params['query'] = $queryResult;
1648
            } else {
1649
                // Something wrong with, remove it
1650
                unset($params['query']);
1651
            }
1652
        }
1653
1654
        // Fix up known synonym/mismatch field names
1655
        foreach ($synonyms as $expected => $replace) {
1656
            if (array_key_exists($expected, $params) && !isset($params[$replace])) {
1657
                // add expected key name
1658
                $params[$replace] = $params[$expected];
1659
            }
1660
        }
1661
1662
        // see if active date set, so we can use it
1663
        $updateLastActive = false;
1664
        $lastActive       = new \DateTime();
1665
        // should be: yyyy-mm-ddT00:00:00+00:00
1666
        if (isset($params['lastActive'])) {
1667
            $lastActive       = new \DateTime($params['lastActive']);
1668
            $updateLastActive = true;
1669
        }
1670
        $params['date_added'] = $lastActive;
1671
1672
        // New utmTag
1673
        $utmTags = new UtmTag();
1674
1675
        // get available fields and their setter.
1676
        $fields = $utmTags->getFieldSetterList();
1677
1678
        // cycle through calling appropriate setter
1679
        foreach ($fields as $q => $setter) {
1680
            if (isset($params[$q])) {
1681
                $utmTags->$setter($params[$q]);
1682
            }
1683
        }
1684
1685
        // create device
1686
        if (!empty($params['useragent'])) {
1687
            $this->deviceTracker->createDeviceFromUserAgent($lead, $params['useragent']);
1688
        }
1689
1690
        // add the lead
1691
        $utmTags->setLead($lead);
1692
        if ($updateLastActive) {
1693
            $lead->setLastActive($lastActive);
1694
        }
1695
1696
        $this->setUtmTags($lead, $utmTags);
1697
    }
1698
1699
    /**
1700
     * Removes a UtmTag set from a Lead.
1701
     *
1702
     * @param int $utmId
1703
     */
1704
    public function removeUtmTags(Lead $lead, $utmId)
1705
    {
1706
        /** @var UtmTag $utmTag */
1707
        foreach ($lead->getUtmTags() as $utmTag) {
1708
            if ($utmTag->getId() === $utmId) {
1709
                $lead->removeUtmTagEntry($utmTag);
1710
                $this->saveEntity($lead);
1711
1712
                return true;
1713
            }
1714
        }
1715
1716
        return false;
1717
    }
1718
1719
    /**
1720
     * Modify tags with support to remove via a prefixed minus sign.
1721
     *
1722
     * @param $tags
1723
     * @param $removeTags
1724
     * @param $persist
1725
     * @param bool True if tags modified
1726
     *
1727
     * @return bool
1728
     */
1729
    public function modifyTags(Lead $lead, $tags, array $removeTags = null, $persist = true)
1730
    {
1731
        $tagsModified = false;
1732
        $leadTags     = $lead->getTags();
1733
1734
        if (!$leadTags->isEmpty()) {
1735
            $this->logger->debug('CONTACT: Contact currently has tags '.implode(', ', $leadTags->getKeys()));
1736
        } else {
1737
            $this->logger->debug('CONTACT: Contact currently does not have any tags');
1738
        }
1739
1740
        if (!is_array($tags)) {
1741
            $tags = explode(',', $tags);
1742
        }
1743
1744
        if (empty($tags) && empty($removeTags)) {
1745
            return false;
1746
        }
1747
1748
        $this->logger->debug('CONTACT: Adding '.implode(', ', $tags).' to contact ID# '.$lead->getId());
1749
1750
        array_walk($tags, function (&$val) {
1751
            $val = html_entity_decode(trim($val), ENT_QUOTES);
1752
            $val = InputHelper::clean($val);
1753
        });
1754
1755
        // See which tags already exist
1756
        $foundTags = $this->getTagRepository()->getTagsByName($tags);
1757
        foreach ($tags as $tag) {
1758
            if (0 === strpos($tag, '-')) {
1759
                // Tag to be removed
1760
                $tag = substr($tag, 1);
1761
1762
                if (array_key_exists($tag, $foundTags) && $leadTags->contains($foundTags[$tag])) {
1763
                    $tagsModified = true;
1764
                    $lead->removeTag($foundTags[$tag]);
1765
1766
                    $this->logger->debug('CONTACT: Removed '.$tag);
1767
                }
1768
            } else {
1769
                $tagToBeAdded = null;
1770
1771
                if (!array_key_exists($tag, $foundTags)) {
1772
                    $tagToBeAdded = new Tag($tag, false);
1773
                } elseif (!$leadTags->contains($foundTags[$tag])) {
1774
                    $tagToBeAdded = $foundTags[$tag];
1775
                }
1776
1777
                if ($tagToBeAdded) {
1778
                    $lead->addTag($tagToBeAdded);
1779
                    $tagsModified = true;
1780
                    $this->logger->debug('CONTACT: Added '.$tag);
1781
                }
1782
            }
1783
        }
1784
1785
        if (!empty($removeTags)) {
1786
            $this->logger->debug('CONTACT: Removing '.implode(', ', $removeTags).' for contact ID# '.$lead->getId());
1787
1788
            array_walk($removeTags, function (&$val) {
1789
                $val = html_entity_decode(trim($val), ENT_QUOTES);
1790
                $val = InputHelper::clean($val);
1791
            });
1792
1793
            // See which tags really exist
1794
            $foundRemoveTags = $this->getTagRepository()->getTagsByName($removeTags);
1795
1796
            foreach ($removeTags as $tag) {
1797
                // Tag to be removed
1798
                if (array_key_exists($tag, $foundRemoveTags) && $leadTags->contains($foundRemoveTags[$tag])) {
1799
                    $lead->removeTag($foundRemoveTags[$tag]);
1800
                    $tagsModified = true;
1801
1802
                    $this->logger->debug('CONTACT: Removed '.$tag);
1803
                }
1804
            }
1805
        }
1806
1807
        if ($persist) {
1808
            $this->saveEntity($lead);
1809
        }
1810
1811
        return $tagsModified;
1812
    }
1813
1814
    /**
1815
     * Modify companies for lead.
1816
     *
1817
     * @param $companies
1818
     */
1819
    public function modifyCompanies(Lead $lead, $companies)
1820
    {
1821
        // See which companies belong to the lead already
1822
        $leadCompanies = $this->companyModel->getCompanyLeadRepository()->getCompaniesByLeadId($lead->getId());
1823
1824
        foreach ($leadCompanies as $leadCompany) {
1825
            if (false === array_search($leadCompany['company_id'], $companies)) {
1826
                $this->companyModel->removeLeadFromCompany([$leadCompany['company_id']], $lead);
1827
            }
1828
        }
1829
1830
        if (count($companies)) {
1831
            $this->companyModel->addLeadToCompany($companies, $lead);
1832
        } else {
1833
            // update the lead's company name to nothing
1834
            $lead->addUpdatedField('company', '');
1835
            $this->getRepository()->saveEntity($lead);
1836
        }
1837
    }
1838
1839
    /**
1840
     * Get array of available lead tags.
1841
     */
1842
    public function getTagList()
1843
    {
1844
        return $this->getTagRepository()->getSimpleList(null, [], 'tag', 'id');
1845
    }
1846
1847
    /**
1848
     * Get bar chart data of contacts.
1849
     *
1850
     * @param string    $unit          {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters}
1851
     * @param \DateTime $dateFrom
1852
     * @param \DateTime $dateTo
1853
     * @param string    $dateFormat
1854
     * @param array     $filter
1855
     * @param bool      $canViewOthers
1856
     *
1857
     * @return array
1858
     */
1859
    public function getLeadsLineChartData($unit, $dateFrom, $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true)
1860
    {
1861
        $flag        = null;
1862
        $topLists    = null;
1863
        $allLeadsT   = $this->translator->trans('mautic.lead.all.leads');
1864
        $identifiedT = $this->translator->trans('mautic.lead.identified');
1865
        $anonymousT  = $this->translator->trans('mautic.lead.lead.anonymous');
1866
1867
        if (isset($filter['flag'])) {
1868
            $flag = $filter['flag'];
1869
            unset($filter['flag']);
1870
        }
1871
1872
        if (!$canViewOthers) {
1873
            $filter['owner_id'] = $this->userHelper->getUser()->getId();
1874
        }
1875
1876
        $chart                              = new LineChart($unit, $dateFrom, $dateTo, $dateFormat);
1877
        $query                              = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
1878
        $anonymousFilter                    = $filter;
1879
        $anonymousFilter['date_identified'] = [
1880
            'expression' => 'isNull',
1881
        ];
1882
        $identifiedFilter                    = $filter;
1883
        $identifiedFilter['date_identified'] = [
1884
            'expression' => 'isNotNull',
1885
        ];
1886
1887
        if ('top' == $flag) {
1888
            $topLists = $this->leadListModel->getTopLists(6, $dateFrom, $dateTo);
1889
            if ($topLists) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $topLists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1890
                foreach ($topLists as $list) {
1891
                    $filter['leadlist_id'] = [
1892
                        'value'            => $list['id'],
1893
                        'list_column_name' => 't.id',
1894
                    ];
1895
                    $all = $query->fetchTimeData('leads', 'date_added', $filter);
1896
                    $chart->setDataset($list['name'].': '.$allLeadsT, $all);
1897
                }
1898
            }
1899
        } elseif ('topIdentifiedVsAnonymous' == $flag) {
1900
            $topLists = $this->leadListModel->getTopLists(3, $dateFrom, $dateTo);
1901
            if ($topLists) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $topLists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1902
                foreach ($topLists as $list) {
1903
                    $anonymousFilter['leadlist_id'] = [
1904
                        'value'            => $list['id'],
1905
                        'list_column_name' => 't.id',
1906
                    ];
1907
                    $identifiedFilter['leadlist_id'] = [
1908
                        'value'            => $list['id'],
1909
                        'list_column_name' => 't.id',
1910
                    ];
1911
                    $identified = $query->fetchTimeData('leads', 'date_added', $identifiedFilter);
1912
                    $anonymous  = $query->fetchTimeData('leads', 'date_added', $anonymousFilter);
1913
                    $chart->setDataset($list['name'].': '.$identifiedT, $identified);
1914
                    $chart->setDataset($list['name'].': '.$anonymousT, $anonymous);
1915
                }
1916
            }
1917
        } elseif ('identified' == $flag) {
1918
            $identified = $query->fetchTimeData('leads', 'date_added', $identifiedFilter);
1919
            $chart->setDataset($identifiedT, $identified);
1920
        } elseif ('anonymous' == $flag) {
1921
            $anonymous = $query->fetchTimeData('leads', 'date_added', $anonymousFilter);
1922
            $chart->setDataset($anonymousT, $anonymous);
1923
        } elseif ('identifiedVsAnonymous' == $flag) {
1924
            $identified = $query->fetchTimeData('leads', 'date_added', $identifiedFilter);
1925
            $anonymous  = $query->fetchTimeData('leads', 'date_added', $anonymousFilter);
1926
            $chart->setDataset($identifiedT, $identified);
1927
            $chart->setDataset($anonymousT, $anonymous);
1928
        } else {
1929
            $all = $query->fetchTimeData('leads', 'date_added', $filter);
1930
            $chart->setDataset($allLeadsT, $all);
1931
        }
1932
1933
        return $chart->render();
1934
    }
1935
1936
    /**
1937
     * Get pie chart data of dwell times.
1938
     *
1939
     * @param string $dateFrom
1940
     * @param string $dateTo
1941
     * @param array  $filters
1942
     * @param bool   $canViewOthers
1943
     *
1944
     * @return array
1945
     */
1946
    public function getAnonymousVsIdentifiedPieChartData($dateFrom, $dateTo, $filters = [], $canViewOthers = true)
1947
    {
1948
        $chart = new PieChart();
1949
        $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
1950
1951
        if (!$canViewOthers) {
1952
            $filter['owner_id'] = $this->userHelper->getUser()->getId();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$filter was never initialized. Although not strictly required by PHP, it is generally a good practice to add $filter = array(); before regardless.
Loading history...
1953
        }
1954
1955
        $identified = $query->count('leads', 'date_identified', 'date_added', $filters);
1956
        $all        = $query->count('leads', 'id', 'date_added', $filters);
1957
        $chart->setDataset($this->translator->trans('mautic.lead.identified'), $identified);
1958
        $chart->setDataset($this->translator->trans('mautic.lead.lead.anonymous'), ($all - $identified));
1959
1960
        return $chart->render();
1961
    }
1962
1963
    /**
1964
     * Get leads count per country name.
1965
     * Can't use entity, because country is a custom field.
1966
     *
1967
     * @param string $dateFrom
1968
     * @param string $dateTo
1969
     * @param array  $filters
1970
     * @param bool   $canViewOthers
1971
     *
1972
     * @return array
1973
     */
1974
    public function getLeadMapData($dateFrom, $dateTo, $filters = [], $canViewOthers = true)
1975
    {
1976
        if (!$canViewOthers) {
1977
            $filter['owner_id'] = $this->userHelper->getUser()->getId();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$filter was never initialized. Although not strictly required by PHP, it is generally a good practice to add $filter = array(); before regardless.
Loading history...
1978
        }
1979
1980
        $q = $this->em->getConnection()->createQueryBuilder();
1981
        $q->select('COUNT(t.id) as quantity, t.country')
1982
            ->from(MAUTIC_TABLE_PREFIX.'leads', 't')
1983
            ->groupBy('t.country')
1984
            ->where($q->expr()->isNotNull('t.country'));
1985
1986
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
1987
        $chartQuery->applyFilters($q, $filters);
1988
        $chartQuery->applyDateFilters($q, 'date_added');
1989
1990
        $results = $q->execute()->fetchAll();
1991
1992
        $countries = array_flip(Intl::getRegionBundle()->getCountryNames('en'));
1993
        $mapData   = [];
1994
1995
        // Convert country names to 2-char code
1996
        if ($results) {
1997
            foreach ($results as $leadCountry) {
1998
                if (isset($countries[$leadCountry['country']])) {
1999
                    $mapData[$countries[$leadCountry['country']]] = $leadCountry['quantity'];
2000
                }
2001
            }
2002
        }
2003
2004
        return $mapData;
2005
    }
2006
2007
    /**
2008
     * Get a list of top (by leads owned) users.
2009
     *
2010
     * @param int    $limit
2011
     * @param string $dateFrom
2012
     * @param string $dateTo
2013
     * @param array  $filters
2014
     *
2015
     * @return array
2016
     */
2017
    public function getTopOwners($limit = 10, $dateFrom = null, $dateTo = null, $filters = [])
2018
    {
2019
        $q = $this->em->getConnection()->createQueryBuilder();
2020
        $q->select('COUNT(t.id) AS leads, t.owner_id, u.first_name, u.last_name')
2021
            ->from(MAUTIC_TABLE_PREFIX.'leads', 't')
2022
            ->join('t', MAUTIC_TABLE_PREFIX.'users', 'u', 'u.id = t.owner_id')
2023
            ->where($q->expr()->isNotNull('t.owner_id'))
2024
            ->orderBy('leads', 'DESC')
2025
            ->groupBy('t.owner_id, u.first_name, u.last_name')
2026
            ->setMaxResults($limit);
2027
2028
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
2029
        $chartQuery->applyFilters($q, $filters);
2030
        $chartQuery->applyDateFilters($q, 'date_added');
2031
2032
        return $q->execute()->fetchAll();
2033
    }
2034
2035
    /**
2036
     * Get a list of top (by leads owned) users.
2037
     *
2038
     * @param int    $limit
2039
     * @param string $dateFrom
2040
     * @param string $dateTo
2041
     * @param array  $filters
2042
     *
2043
     * @return array
2044
     */
2045
    public function getTopCreators($limit = 10, $dateFrom = null, $dateTo = null, $filters = [])
2046
    {
2047
        $q = $this->em->getConnection()->createQueryBuilder();
2048
        $q->select('COUNT(t.id) AS leads, t.created_by, t.created_by_user')
2049
            ->from(MAUTIC_TABLE_PREFIX.'leads', 't')
2050
            ->where($q->expr()->isNotNull('t.created_by'))
2051
            ->andWhere($q->expr()->isNotNull('t.created_by_user'))
2052
            ->orderBy('leads', 'DESC')
2053
            ->groupBy('t.created_by, t.created_by_user')
2054
            ->setMaxResults($limit);
2055
2056
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
2057
        $chartQuery->applyFilters($q, $filters);
2058
        $chartQuery->applyDateFilters($q, 'date_added');
2059
2060
        return $q->execute()->fetchAll();
2061
    }
2062
2063
    /**
2064
     * Get a list of leads in a date range.
2065
     *
2066
     * @param int       $limit
2067
     * @param \DateTime $dateFrom
2068
     * @param \DateTime $dateTo
2069
     * @param array     $filters
2070
     * @param array     $options
2071
     *
2072
     * @return array
2073
     */
2074
    public function getLeadList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = [])
2075
    {
2076
        if (!empty($options['canViewOthers'])) {
2077
            $filter['owner_id'] = $this->userHelper->getUser()->getId();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$filter was never initialized. Although not strictly required by PHP, it is generally a good practice to add $filter = array(); before regardless.
Loading history...
2078
        }
2079
2080
        $q = $this->em->getConnection()->createQueryBuilder();
2081
        $q->select('t.id, t.firstname, t.lastname, t.email, t.date_added, t.date_modified')
2082
            ->from(MAUTIC_TABLE_PREFIX.'leads', 't')
2083
            ->setMaxResults($limit);
2084
2085
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

2085
        $chartQuery = new ChartQuery($this->em->getConnection(), /** @scrutinizer ignore-type */ $dateFrom, $dateTo);
Loading history...
Bug introduced by
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 ignore-type  annotation

2085
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, /** @scrutinizer ignore-type */ $dateTo);
Loading history...
2086
        $chartQuery->applyFilters($q, $filters);
2087
        $chartQuery->applyDateFilters($q, 'date_added');
2088
2089
        if (empty($options['includeAnonymous'])) {
2090
            $q->andWhere($q->expr()->isNotNull('t.date_identified'));
2091
        }
2092
        $results = $q->execute()->fetchAll();
2093
2094
        if ($results) {
2095
            foreach ($results as &$result) {
2096
                if ($result['firstname'] || $result['lastname']) {
2097
                    $result['name'] = trim($result['firstname'].' '.$result['lastname']);
2098
                } elseif ($result['email']) {
2099
                    $result['name'] = $result['email'];
2100
                } else {
2101
                    $result['name'] = 'anonymous';
2102
                }
2103
                unset($result['firstname']);
2104
                unset($result['lastname']);
2105
                unset($result['email']);
2106
            }
2107
        }
2108
2109
        return $results;
2110
    }
2111
2112
    /**
2113
     * Get timeline/engagement data.
2114
     *
2115
     * @param null $filters
2116
     * @param int  $page
2117
     * @param int  $limit
2118
     * @param bool $forTimeline
2119
     *
2120
     * @return array
2121
     */
2122
    public function getEngagements(Lead $lead = null, $filters = null, array $orderBy = null, $page = 1, $limit = 25, $forTimeline = true)
2123
    {
2124
        $event = $this->dispatcher->dispatch(
2125
            LeadEvents::TIMELINE_ON_GENERATE,
2126
            new LeadTimelineEvent($lead, $filters, $orderBy, $page, $limit, $forTimeline, $this->coreParametersHelper->get('site_url'))
2127
        );
2128
2129
        $payload = [
2130
            'events'   => $event->getEvents(),
0 ignored issues
show
Bug introduced by
The method getEvents() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as Mautic\WebhookBundle\Event\WebhookBuilderEvent or Mautic\CampaignBundle\Event\CampaignDecisionEvent or Mautic\PointBundle\Event\TriggerBuilderEvent or Mautic\CalendarBundle\Event\CalendarGeneratorEvent or Mautic\LeadBundle\Event\LeadTimelineEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2130
            'events'   => $event->/** @scrutinizer ignore-call */ getEvents(),
Loading history...
2131
            'filters'  => $filters,
2132
            'order'    => $orderBy,
2133
            'types'    => $event->getEventTypes(),
0 ignored issues
show
Bug introduced by
The method getEventTypes() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as Mautic\LeadBundle\Event\LeadTimelineEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2133
            'types'    => $event->/** @scrutinizer ignore-call */ getEventTypes(),
Loading history...
2134
            'total'    => $event->getEventCounter()['total'],
0 ignored issues
show
Bug introduced by
The method getEventCounter() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as Mautic\LeadBundle\Event\LeadTimelineEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2134
            'total'    => $event->/** @scrutinizer ignore-call */ getEventCounter()['total'],
Loading history...
2135
            'page'     => $page,
2136
            'limit'    => $limit,
2137
            'maxPages' => $event->getMaxPage(),
0 ignored issues
show
Bug introduced by
The method getMaxPage() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as Mautic\LeadBundle\Event\LeadTimelineEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2137
            'maxPages' => $event->/** @scrutinizer ignore-call */ getMaxPage(),
Loading history...
2138
        ];
2139
2140
        return ($forTimeline) ? $payload : [$payload, $event->getSerializerGroups()];
0 ignored issues
show
Bug introduced by
The method getSerializerGroups() does not exist on Symfony\Component\EventDispatcher\Event. It seems like you code against a sub-type of Symfony\Component\EventDispatcher\Event such as Mautic\LeadBundle\Event\LeadTimelineEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2140
        return ($forTimeline) ? $payload : [$payload, $event->/** @scrutinizer ignore-call */ getSerializerGroups()];
Loading history...
2141
    }
2142
2143
    /**
2144
     * @return array
2145
     */
2146
    public function getEngagementTypes()
2147
    {
2148
        $event = new LeadTimelineEvent();
2149
        $event->fetchTypesOnly();
2150
2151
        $this->dispatcher->dispatch(LeadEvents::TIMELINE_ON_GENERATE, $event);
2152
2153
        return $event->getEventTypes();
2154
    }
2155
2156
    /**
2157
     * Get engagement counts by time unit.
2158
     *
2159
     * @param string $unit
2160
     *
2161
     * @return array
2162
     */
2163
    public function getEngagementCount(Lead $lead, \DateTime $dateFrom = null, \DateTime $dateTo = null, $unit = 'm', ChartQuery $chartQuery = null)
2164
    {
2165
        $event = new LeadTimelineEvent($lead);
2166
        $event->setCountOnly($dateFrom, $dateTo, $unit, $chartQuery);
0 ignored issues
show
Bug introduced by
It seems like $dateTo can also be of type null; however, parameter $dateTo of Mautic\LeadBundle\Event\...neEvent::setCountOnly() 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 ignore-type  annotation

2166
        $event->setCountOnly($dateFrom, /** @scrutinizer ignore-type */ $dateTo, $unit, $chartQuery);
Loading history...
Bug introduced by
It seems like $dateFrom can also be of type null; however, parameter $dateFrom of Mautic\LeadBundle\Event\...neEvent::setCountOnly() 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 ignore-type  annotation

2166
        $event->setCountOnly(/** @scrutinizer ignore-type */ $dateFrom, $dateTo, $unit, $chartQuery);
Loading history...
2167
2168
        $this->dispatcher->dispatch(LeadEvents::TIMELINE_ON_GENERATE, $event);
2169
2170
        return $event->getEventCounter();
2171
    }
2172
2173
    /**
2174
     * @param $company
2175
     *
2176
     * @return bool
2177
     */
2178
    public function addToCompany(Lead $lead, $company)
2179
    {
2180
        //check if lead is in company already
2181
        if (!$company instanceof Company) {
2182
            $company = $this->companyModel->getEntity($company);
2183
        }
2184
2185
        // company does not exist anymore
2186
        if (null === $company) {
2187
            return false;
2188
        }
2189
2190
        $companyLead = $this->companyModel->getCompanyLeadRepository()->getCompaniesByLeadId($lead->getId(), $company->getId());
2191
2192
        if (empty($companyLead)) {
2193
            $this->companyModel->addLeadToCompany($company, $lead);
2194
2195
            return true;
2196
        }
2197
2198
        return false;
2199
    }
2200
2201
    /**
2202
     * Get contact channels.
2203
     *
2204
     * @return array
2205
     */
2206
    public function getContactChannels(Lead $lead)
2207
    {
2208
        $allChannels = $this->getPreferenceChannels();
2209
2210
        $channels = [];
2211
        foreach ($allChannels as $channel) {
2212
            if (DNC::IS_CONTACTABLE === $this->isContactable($lead, $channel)) {
0 ignored issues
show
Deprecated Code introduced by
The function Mautic\LeadBundle\Model\LeadModel::isContactable() has been deprecated: 2.12.0 to be removed in 3.0; use Mautic\LeadBundle\Model\DoNotContact instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2212
            if (DNC::IS_CONTACTABLE === /** @scrutinizer ignore-deprecated */ $this->isContactable($lead, $channel)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2213
                $channels[$channel] = $channel;
2214
            }
2215
        }
2216
2217
        return $channels;
2218
    }
2219
2220
    /**
2221
     * Get contact channels.
2222
     *
2223
     * @return array
2224
     */
2225
    public function getDoNotContactChannels(Lead $lead)
2226
    {
2227
        $allChannels = $this->getPreferenceChannels();
2228
2229
        $channels = [];
2230
        foreach ($allChannels as $channel) {
2231
            if (DNC::IS_CONTACTABLE !== $this->isContactable($lead, $channel)) {
0 ignored issues
show
Deprecated Code introduced by
The function Mautic\LeadBundle\Model\LeadModel::isContactable() has been deprecated: 2.12.0 to be removed in 3.0; use Mautic\LeadBundle\Model\DoNotContact instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2231
            if (DNC::IS_CONTACTABLE !== /** @scrutinizer ignore-deprecated */ $this->isContactable($lead, $channel)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2232
                $channels[$channel] = $channel;
2233
            }
2234
        }
2235
2236
        return $channels;
2237
    }
2238
2239
    /**
2240
     * @return array
2241
     */
2242
    public function getPreferenceChannels()
2243
    {
2244
        return $this->channelListHelper->getFeatureChannels(self::CHANNEL_FEATURE, true);
2245
    }
2246
2247
    /**
2248
     * @return array
2249
     */
2250
    public function getPreferredChannel(Lead $lead)
2251
    {
2252
        $preferredChannel = $this->getFrequencyRuleRepository()->getPreferredChannel($lead->getId());
2253
        if (!empty($preferredChannel)) {
2254
            return $preferredChannel[0];
2255
        }
2256
2257
        return [];
2258
    }
2259
2260
    /**
2261
     * @param $companyId
2262
     * @param $leadId
2263
     *
2264
     * @return array
2265
     */
2266
    public function setPrimaryCompany($companyId, $leadId)
2267
    {
2268
        $companyArray      = [];
2269
        $oldPrimaryCompany = $newPrimaryCompany = false;
2270
2271
        $lead = $this->getEntity($leadId);
2272
2273
        $companyLeads = $this->companyModel->getCompanyLeadRepository()->getEntitiesByLead($lead);
2274
2275
        /** @var CompanyLead $companyLead */
2276
        foreach ($companyLeads as $companyLead) {
2277
            $company = $companyLead->getCompany();
2278
2279
            if ($companyLead) {
2280
                if ($companyLead->getPrimary() && !$oldPrimaryCompany) {
2281
                    $oldPrimaryCompany = $companyLead->getCompany()->getId();
2282
                }
2283
                if ($company->getId() === (int) $companyId) {
2284
                    $companyLead->setPrimary(true);
2285
                    $newPrimaryCompany = $companyId;
2286
                    $lead->addUpdatedField('company', $company->getName());
2287
                } else {
2288
                    $companyLead->setPrimary(false);
2289
                }
2290
                $companyArray[] = $companyLead;
2291
            }
2292
        }
2293
2294
        if (!$newPrimaryCompany) {
2295
            $latestCompany = $this->companyModel->getCompanyLeadRepository()->getLatestCompanyForLead($leadId);
2296
            if (!empty($latestCompany)) {
2297
                $lead->addUpdatedField('company', $latestCompany['companyname'])
2298
                    ->setDateModified(new \DateTime());
2299
            }
2300
        }
2301
2302
        if (!empty($companyArray)) {
2303
            $this->em->getRepository('MauticLeadBundle:Lead')->saveEntity($lead);
2304
            $this->companyModel->getCompanyLeadRepository()->saveEntities($companyArray, false);
2305
        }
2306
2307
        // Clear CompanyLead entities from Doctrine memory
2308
        $this->em->clear(CompanyLead::class);
2309
2310
        return ['oldPrimary' => $oldPrimaryCompany, 'newPrimary' => $companyId];
2311
    }
2312
2313
    /**
2314
     * @param $score
2315
     *
2316
     * @return bool
2317
     */
2318
    public function scoreContactsCompany(Lead $lead, $score)
2319
    {
2320
        $success          = false;
2321
        $entities         = [];
2322
        $contactCompanies = $this->companyModel->getCompanyLeadRepository()->getCompaniesByLeadId($lead->getId());
2323
2324
        if (!empty($contactCompanies)) {
2325
            foreach ($contactCompanies as $contactCompany) {
2326
                $company  = $this->companyModel->getEntity($contactCompany['company_id']);
2327
                $oldScore = $company->getScore();
2328
                $newScore = $score + $oldScore;
2329
                $company->setScore($newScore);
2330
                $entities[] = $company;
2331
                $success    = true;
2332
            }
2333
        }
2334
2335
        if (!empty($entities)) {
2336
            $this->companyModel->getRepository()->saveEntities($entities);
2337
        }
2338
2339
        return $success;
2340
    }
2341
2342
    /**
2343
     * @param $ownerId
2344
     */
2345
    public function updateLeadOwner(Lead $lead, $ownerId)
2346
    {
2347
        $owner = $this->em->getReference(User::class, $ownerId);
2348
        $lead->setOwner($owner);
2349
2350
        parent::saveEntity($lead);
2351
    }
2352
2353
    private function processManipulator(Lead $lead)
2354
    {
2355
        if ($lead->isNewlyCreated() || $lead->wasAnonymous()) {
2356
            // Only store an entry once for created and once for identified, not every time the lead is saved
2357
            $manipulator = $lead->getManipulator();
2358
            if (null !== $manipulator && !$manipulator->wasLogged()) {
2359
                $manipulationLog = new LeadEventLog();
2360
                $manipulationLog->setLead($lead)
2361
                    ->setBundle($manipulator->getBundleName())
2362
                    ->setObject($manipulator->getObjectName())
2363
                    ->setObjectId($manipulator->getObjectId());
2364
                if ($lead->isAnonymous()) {
2365
                    $manipulationLog->setAction('created_contact');
2366
                } else {
2367
                    $manipulationLog->setAction('identified_contact');
2368
                }
2369
                $description = $manipulator->getObjectDescription();
2370
                $manipulationLog->setProperties(['object_description' => $description]);
2371
2372
                $lead->addEventLog($manipulationLog);
2373
                $manipulator->setAsLogged();
2374
            }
2375
        }
2376
    }
2377
2378
    /**
2379
     * @param bool $persist
2380
     *
2381
     * @return Lead
2382
     */
2383
    protected function createNewContact(IpAddress $ip, $persist = true)
2384
    {
2385
        //let's create a lead
2386
        $lead = new Lead();
2387
        $lead->addIpAddress($ip);
2388
        $lead->setNewlyCreated(true);
2389
2390
        if ($persist && !defined('MAUTIC_NON_TRACKABLE_REQUEST')) {
2391
            // Set to prevent loops
2392
            $this->contactTracker->setTrackedContact($lead);
2393
2394
            // Note ignoring a lead manipulator object here on purpose to not falsely record entries
2395
            $this->saveEntity($lead, false);
2396
2397
            $fields = $this->getLeadDetails($lead);
2398
            $lead->setFields($fields);
2399
        }
2400
2401
        if ($leadId = $lead->getId()) {
2402
            $this->logger->addDebug("LEAD: New lead created with ID# $leadId.");
2403
        }
2404
2405
        return $lead;
2406
    }
2407
2408
    /**
2409
     * @deprecated 2.12.0 to be removed in 3.0; use Mautic\LeadBundle\Model\DoNotContact instead
2410
     *
2411
     * @param string $channel
2412
     *
2413
     * @return int
2414
     *
2415
     * @see \Mautic\LeadBundle\Entity\DoNotContact This method can return boolean false, so be
2416
     *                                             sure to always compare the return value against
2417
     *                                             the class constants of DoNotContact
2418
     */
2419
    public function isContactable(Lead $lead, $channel)
2420
    {
2421
        if (is_array($channel)) {
0 ignored issues
show
introduced by
The condition is_array($channel) is always false.
Loading history...
2422
            $channel = key($channel);
2423
        }
2424
2425
        /** @var \Mautic\LeadBundle\Entity\DoNotContactRepository $dncRepo */
2426
        $dncRepo = $this->em->getRepository('MauticLeadBundle:DoNotContact');
2427
2428
        /** @var \Mautic\LeadBundle\Entity\DoNotContact[] $entries */
2429
        $dncEntries = $dncRepo->getEntriesByLeadAndChannel($lead, $channel);
2430
2431
        // If the lead has no entries in the DNC table, we're good to go
2432
        if (empty($dncEntries)) {
2433
            return DNC::IS_CONTACTABLE;
2434
        }
2435
2436
        foreach ($dncEntries as $dnc) {
2437
            if (DNC::IS_CONTACTABLE !== $dnc->getReason()) {
2438
                return $dnc->getReason();
2439
            }
2440
        }
2441
2442
        return DNC::IS_CONTACTABLE;
2443
    }
2444
2445
    /**
2446
     * Merge two leads; if a conflict of data occurs, the newest lead will get precedence.
2447
     *
2448
     * @deprecated 2.13.0; to be removed in 3.0. Use \Mautic\LeadBundle\Deduplicate\ContactMerger instead
2449
     *
2450
     * @param bool $autoMode If true, the newest lead will be merged into the oldes then deleted; otherwise, $lead will be merged into $lead2 then deleted
2451
     *
2452
     * @return Lead
2453
     */
2454
    public function mergeLeads(Lead $lead, Lead $lead2, $autoMode = true)
2455
    {
2456
        return $this->legacyLeadModel->mergeLeads($lead, $lead2, $autoMode);
2457
    }
2458
2459
    public function getAvailableLeadFields(): array
2460
    {
2461
        return $this->availableLeadFields;
2462
    }
2463
}
2464