Issues (3627)

app/bundles/LeadBundle/Entity/Lead.php (3 issues)

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\Entity;
13
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\ORM\Mapping as ORM;
16
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
17
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
18
use Mautic\CoreBundle\Entity\FormEntity;
19
use Mautic\CoreBundle\Entity\IpAddress;
20
use Mautic\LeadBundle\DataObject\LeadManipulator;
21
use Mautic\LeadBundle\Model\FieldModel;
22
use Mautic\NotificationBundle\Entity\PushID;
23
use Mautic\StageBundle\Entity\Stage;
24
use Mautic\UserBundle\Entity\User;
25
26
class Lead extends FormEntity implements CustomFieldEntityInterface
27
{
28
    use CustomFieldEntityTrait;
29
30
    const FIELD_ALIAS     = '';
31
    const POINTS_ADD      = 'plus';
32
    const POINTS_SUBTRACT = 'minus';
33
    const POINTS_MULTIPLY = 'times';
34
    const POINTS_DIVIDE   = 'divide';
35
36
    /**
37
     * Used to determine social identity.
38
     *
39
     * @var array
40
     */
41
    private $availableSocialFields = [];
42
43
    /**
44
     * @var int
45
     */
46
    private $id;
47
48
    private $title;
49
50
    private $firstname;
51
52
    private $lastname;
53
54
    private $company;
55
56
    private $position;
57
58
    private $email;
59
60
    private $phone;
61
62
    private $mobile;
63
64
    private $address1;
65
66
    private $address2;
67
68
    private $city;
69
70
    private $state;
71
72
    private $zipcode;
73
74
    /**
75
     * @var string
76
     */
77
    private $timezone;
78
79
    private $country;
80
81
    /**
82
     * @var User
83
     */
84
    private $owner;
85
86
    /**
87
     * @var int
88
     */
89
    private $points = 0;
90
91
    /**
92
     * @var array
93
     */
94
    private $pointChanges = [];
95
96
    /**
97
     * @var int|null
98
     */
99
    private $updatedPoints;
100
101
    /**
102
     * @var ArrayCollection
103
     */
104
    private $pointsChangeLog;
105
106
    /**
107
     * @var null
108
     */
109
    private $actualPoints;
110
111
    /**
112
     * @var ArrayCollection
113
     */
114
    private $companyChangeLog;
115
116
    /**
117
     * @var ArrayCollection
118
     */
119
    private $doNotContact;
120
121
    /**
122
     * @var ArrayCollection
123
     */
124
    private $ipAddresses;
125
126
    /**
127
     * @var ArrayCollection
128
     */
129
    private $pushIds;
130
131
    /**
132
     * @var ArrayCollection
133
     */
134
    private $eventLog;
135
136
    /**
137
     * @var \DateTime
138
     */
139
    private $lastActive;
140
141
    /**
142
     * @var array
143
     */
144
    private $internal = [];
145
146
    /**
147
     * @var array
148
     */
149
    private $socialCache = [];
150
151
    /**
152
     * Used to populate trigger color.
153
     *
154
     * @var string
155
     */
156
    private $color;
157
158
    /**
159
     * @var LeadManipulator
160
     */
161
    private $manipulator;
162
163
    /**
164
     * @var bool
165
     */
166
    private $newlyCreated = false;
167
168
    /**
169
     * @var \DateTime
170
     */
171
    private $dateIdentified;
172
173
    /**
174
     * @var ArrayCollection
175
     */
176
    private $notes;
177
178
    /**
179
     * @var string
180
     */
181
    private $preferredProfileImage = 'gravatar';
182
183
    /**
184
     * @var bool
185
     */
186
    public $imported = false;
187
188
    /**
189
     * @var ArrayCollection
190
     */
191
    private $tags;
192
193
    /**
194
     * @var Stage
195
     */
196
    private $stage;
197
198
    /**
199
     * @var ArrayCollection
200
     */
201
    private $stageChangeLog;
202
203
    /**
204
     * @var ArrayCollection
205
     */
206
    private $utmtags;
207
208
    /**
209
     * @var FrequencyRule[]
210
     */
211
    private $frequencyRules;
212
213
    private $primaryCompany;
214
215
    /**
216
     * Used to determine order of preferred channels.
217
     *
218
     * @var array
219
     */
220
    private $channelRules = [];
221
222
    public function __construct()
223
    {
224
        $this->ipAddresses      = new ArrayCollection();
225
        $this->pushIds          = new ArrayCollection();
226
        $this->eventLog         = new ArrayCollection();
227
        $this->doNotContact     = new ArrayCollection();
228
        $this->pointsChangeLog  = new ArrayCollection();
229
        $this->tags             = new ArrayCollection();
230
        $this->stageChangeLog   = new ArrayCollection();
231
        $this->frequencyRules   = new ArrayCollection();
232
        $this->companyChangeLog = new ArrayCollection();
233
    }
234
235
    public static function loadMetadata(ORM\ClassMetadata $metadata)
236
    {
237
        $builder = new ClassMetadataBuilder($metadata);
238
239
        $builder->setTable('leads')
240
            ->setCustomRepositoryClass('Mautic\LeadBundle\Entity\LeadRepository')
241
            ->addLifecycleEvent('checkDateIdentified', 'preUpdate')
242
            ->addLifecycleEvent('checkDateIdentified', 'prePersist')
243
            ->addLifecycleEvent('checkAttributionDate', 'preUpdate')
244
            ->addLifecycleEvent('checkAttributionDate', 'prePersist')
245
            ->addLifecycleEvent('checkDateAdded', 'prePersist')
246
            ->addIndex(['date_added'], 'lead_date_added')
247
            ->addIndex(['date_identified'], 'date_identified');
248
249
        $builder->addBigIntIdField();
250
251
        $builder->createManyToOne('owner', 'Mautic\UserBundle\Entity\User')
252
            ->fetchLazy()
253
            ->addJoinColumn('owner_id', 'id', true, false, 'SET NULL')
254
            ->build();
255
256
        $builder->createField('points', 'integer')
257
            ->build();
258
259
        $builder->createOneToMany('pointsChangeLog', 'PointsChangeLog')
260
            ->orphanRemoval()
261
            ->setOrderBy(['dateAdded' => 'DESC'])
262
            ->mappedBy('lead')
263
            ->cascadeAll()
264
            ->fetchExtraLazy()
265
            ->build();
266
267
        $builder->createOneToMany('companyChangeLog', 'CompanyChangeLog')
268
            ->orphanRemoval()
269
            ->setOrderBy(['dateAdded' => 'DESC'])
270
            ->mappedBy('lead')
271
            ->cascadeAll()
272
            ->fetchExtraLazy()
273
            ->build();
274
275
        $builder->createOneToMany('doNotContact', 'Mautic\LeadBundle\Entity\DoNotContact')
276
            ->orphanRemoval()
277
            ->mappedBy('lead')
278
            ->cascadePersist()
279
            ->cascadeDetach()
280
            ->cascadeMerge()
281
            ->fetchExtraLazy()
282
            ->build();
283
284
        $builder->createManyToMany('ipAddresses', 'Mautic\CoreBundle\Entity\IpAddress')
285
            ->setJoinTable('lead_ips_xref')
286
            ->addInverseJoinColumn('ip_id', 'id', false)
287
            ->addJoinColumn('lead_id', 'id', false, false, 'CASCADE')
288
            ->setIndexBy('ipAddress')
289
            ->cascadeDetach()
290
            ->cascadeMerge()
291
            ->cascadePersist()
292
            ->build();
293
294
        $builder->createOneToMany('pushIds', 'Mautic\NotificationBundle\Entity\PushID')
295
            ->orphanRemoval()
296
            ->mappedBy('lead')
297
            ->cascadeAll()
298
            ->fetchExtraLazy()
299
            ->build();
300
301
        $builder->createOneToMany('eventLog', LeadEventLog::class)
302
            ->mappedBy('lead')
303
            ->cascadePersist()
304
            ->cascadeMerge()
305
            ->cascadeDetach()
306
            ->fetchExtraLazy()
307
            ->build();
308
309
        $builder->createField('lastActive', 'datetime')
310
            ->columnName('last_active')
311
            ->nullable()
312
            ->build();
313
314
        $builder->createField('internal', 'array')
315
            ->nullable()
316
            ->build();
317
318
        $builder->createField('socialCache', 'array')
319
            ->columnName('social_cache')
320
            ->nullable()
321
            ->build();
322
323
        $builder->createField('dateIdentified', 'datetime')
324
            ->columnName('date_identified')
325
            ->nullable()
326
            ->build();
327
328
        $builder->createOneToMany('notes', 'LeadNote')
329
            ->orphanRemoval()
330
            ->setOrderBy(['dateAdded' => 'DESC'])
331
            ->mappedBy('lead')
332
            ->cascadeDetach()
333
            ->cascadeMerge()
334
            ->fetchExtraLazy()
335
            ->build();
336
337
        $builder->createField('preferredProfileImage', 'string')
338
            ->columnName('preferred_profile_image')
339
            ->nullable()
340
            ->build();
341
342
        $builder->createManyToMany('tags', 'Mautic\LeadBundle\Entity\Tag')
343
            ->setJoinTable('lead_tags_xref')
344
            ->addInverseJoinColumn('tag_id', 'id', false)
345
            ->addJoinColumn('lead_id', 'id', false, false, 'CASCADE')
346
            ->setOrderBy(['tag' => 'ASC'])
347
            ->setIndexBy('tag')
348
            ->fetchLazy()
349
            ->cascadeMerge()
350
            ->cascadePersist()
351
            ->cascadeDetach()
352
            ->build();
353
354
        $builder->createManyToOne('stage', 'Mautic\StageBundle\Entity\Stage')
355
            ->cascadePersist()
356
            ->cascadeMerge()
357
            ->cascadeDetach()
358
            ->addJoinColumn('stage_id', 'id', true, false, 'SET NULL')
359
            ->build();
360
361
        $builder->createOneToMany('stageChangeLog', 'StagesChangeLog')
362
            ->orphanRemoval()
363
            ->setOrderBy(['dateAdded' => 'DESC'])
364
            ->mappedBy('lead')
365
            ->cascadeAll()
366
            ->fetchExtraLazy()
367
            ->build();
368
369
        $builder->createOneToMany('utmtags', 'Mautic\LeadBundle\Entity\UtmTag')
370
            ->orphanRemoval()
371
            ->mappedBy('lead')
372
            ->cascadeAll()
373
            ->fetchExtraLazy()
374
            ->build();
375
376
        $builder->createOneToMany('frequencyRules', 'Mautic\LeadBundle\Entity\FrequencyRule')
377
            ->orphanRemoval()
378
            ->setIndexBy('channel')
379
            ->setOrderBy(['dateAdded' => 'DESC'])
380
            ->mappedBy('lead')
381
            ->cascadeAll()
382
            ->fetchExtraLazy()
383
            ->build();
384
385
        self::loadFixedFieldMetadata(
386
            $builder,
387
            [
388
                'title',
389
                'firstname',
390
                'lastname',
391
                'company',
392
                'position',
393
                'email',
394
                'phone',
395
                'mobile',
396
                'address1',
397
                'address2',
398
                'city',
399
                'state',
400
                'zipcode',
401
                'timezone',
402
                'country',
403
            ],
404
            FieldModel::$coreFields
405
        );
406
    }
407
408
    /**
409
     * Prepares the metadata for API usage.
410
     *
411
     * @param $metadata
412
     */
413
    public static function loadApiMetadata(ApiMetadataDriver $metadata)
414
    {
415
        $metadata->setRoot('lead')
416
            ->setGroupPrefix('leadBasic')
417
            ->addListProperties(
418
                [
419
                    'id',
420
                    'points',
421
                    'color',
422
                    'title',
423
                    'firstname',
424
                    'lastname',
425
                    'company',
426
                    'position',
427
                    'email',
428
                    'phone',
429
                    'mobile',
430
                    'address1',
431
                    'address2',
432
                    'city',
433
                    'state',
434
                    'zipcode',
435
                    'timezone',
436
                    'country',
437
                ]
438
            )
439
            ->setGroupPrefix('lead')
440
            ->addListProperties(
441
                [
442
                    'id',
443
                    'points',
444
                    'color',
445
                    'fields',
446
                ]
447
            )
448
            ->addProperties(
449
                [
450
                    'lastActive',
451
                    'owner',
452
                    'ipAddresses',
453
                    'tags',
454
                    'utmtags',
455
                    'stage',
456
                    'dateIdentified',
457
                    'preferredProfileImage',
458
                    'doNotContact',
459
                    'frequencyRules',
460
                ]
461
            )
462
            ->build();
463
    }
464
465
    /**
466
     * @param string $prop
467
     * @param mixed  $val
468
     * @param null   $oldValue
469
     */
470
    protected function isChanged($prop, $val, $oldValue = null)
471
    {
472
        $getter  = 'get'.ucfirst($prop);
473
        $current = null !== $oldValue ? $oldValue : $this->$getter();
474
        if ('owner' == $prop) {
475
            if ($current && !$val) {
476
                $this->changes['owner'] = [$current->getId(), $val];
477
            } elseif (!$current && $val) {
478
                $this->changes['owner'] = [$current, $val->getId()];
479
            } elseif ($current && $val && $current->getId() != $val->getId()) {
480
                $this->changes['owner'] = [$current->getId(), $val->getId()];
481
            }
482
        } elseif ('ipAddresses' == $prop) {
483
            $this->changes['ipAddresses'] = ['', $val->getIpAddress()]; // Kept for BC. Not a good way to track changes on a collection
484
485
            if (empty($this->changes['ipAddressList'])) {
486
                $this->changes['ipAddressList'] = [];
487
            }
488
489
            $this->changes['ipAddressList'][$val->getIpAddress()] = $val;
490
        } elseif ('tags' == $prop) {
491
            if ($val instanceof Tag) {
492
                $this->changes['tags']['added'][] = $val->getTag();
493
            } else {
494
                $this->changes['tags']['removed'][] = $val;
495
            }
496
        } elseif ('utmtags' == $prop) {
497
            if ($val instanceof UtmTag) {
498
                if ($val->getUtmContent()) {
499
                    $this->changes['utmtags'] = ['utm_content', $val->getUtmContent()];
500
                }
501
                if ($val->getUtmMedium()) {
502
                    $this->changes['utmtags'] = ['utm_medium', $val->getUtmMedium()];
503
                }
504
                if ($val->getUtmCampaign()) {
505
                    $this->changes['utmtags'] = ['utm_campaign', $val->getUtmCampaign()];
506
                }
507
                if ($val->getUtmTerm()) {
508
                    $this->changes['utmtags'] = ['utm_term', $val->getUtmTerm()];
509
                }
510
                if ($val->getUtmSource()) {
511
                    $this->changes['utmtags'] = ['utm_source', $val->getUtmSource()];
512
                }
513
            }
514
        } elseif ('frequencyRules' == $prop) {
515
            if (!isset($this->changes['frequencyRules'])) {
516
                $this->changes['frequencyRules'] = [];
517
            }
518
519
            if ($val instanceof FrequencyRule) {
520
                $channel = $val->getChannel();
521
522
                $this->changes['frequencyRules'][$channel] = $val->getChanges();
523
            } else {
524
                $this->changes['frequencyRules']['removed'][] = $val;
525
            }
526
        } elseif ('stage' == $prop) {
527
            if ($current && !$val) {
528
                $this->changes['stage'] = [$current->getId(), $val];
529
            } elseif (!$current && $val) {
530
                $this->changes['stage'] = [$current, $val->getId()];
531
            } elseif ($current && $val && $current->getId() != $val->getId()) {
532
                $this->changes['stage'] = [$current->getId(), $val->getId()];
533
            }
534
        } elseif ('points' == $prop && $current != $val) {
535
            $this->changes['points'] = [$current, $val];
536
        } else {
537
            parent::isChanged($prop, $val);
538
        }
539
    }
540
541
    /**
542
     * @return array
543
     */
544
    public function convertToArray()
545
    {
546
        return get_object_vars($this);
547
    }
548
549
    /**
550
     * Set id.
551
     *
552
     * @param int $id
553
     *
554
     * @return Lead
555
     */
556
    public function setId($id)
557
    {
558
        $this->id = $id;
559
560
        return $this;
561
    }
562
563
    /**
564
     * Get id.
565
     *
566
     * @return int
567
     */
568
    public function getId()
569
    {
570
        return $this->id;
571
    }
572
573
    /**
574
     * Set owner.
575
     *
576
     * @param User $owner
577
     *
578
     * @return Lead
579
     */
580
    public function setOwner(User $owner = null)
581
    {
582
        $this->isChanged('owner', $owner);
583
        $this->owner = $owner;
584
585
        return $this;
586
    }
587
588
    /**
589
     * Get owner.
590
     *
591
     * @return User
592
     */
593
    public function getOwner()
594
    {
595
        return $this->owner;
596
    }
597
598
    /**
599
     * Returns the user to be used for permissions.
600
     *
601
     * @return User|int
602
     */
603
    public function getPermissionUser()
604
    {
605
        return (null === $this->getOwner()) ? $this->getCreatedBy() : $this->getOwner();
606
    }
607
608
    /**
609
     * Add ipAddress.
610
     *
611
     * @return Lead
612
     */
613
    public function addIpAddress(IpAddress $ipAddress)
614
    {
615
        if (!$ipAddress->isTrackable()) {
616
            return $this;
617
        }
618
619
        $ip = $ipAddress->getIpAddress();
620
        if (!isset($this->ipAddresses[$ip])) {
621
            $this->isChanged('ipAddresses', $ipAddress);
622
            $this->ipAddresses[$ip] = $ipAddress;
623
        }
624
625
        return $this;
626
    }
627
628
    /**
629
     * Remove ipAddress.
630
     */
631
    public function removeIpAddress(IpAddress $ipAddress)
632
    {
633
        $this->ipAddresses->removeElement($ipAddress);
634
    }
635
636
    /**
637
     * Get ipAddresses.
638
     *
639
     * @return \Doctrine\Common\Collections\Collection
640
     */
641
    public function getIpAddresses()
642
    {
643
        return $this->ipAddresses;
644
    }
645
646
    /**
647
     * Get full name.
648
     *
649
     * @param bool $lastFirst
650
     *
651
     * @return string
652
     */
653
    public function getName($lastFirst = false)
654
    {
655
        $firstName = $this->getFirstname();
656
        $lastName  = $this->getLastname();
657
658
        $fullName = '';
659
        if ($lastFirst && $firstName && $lastName) {
660
            $fullName = $lastName.', '.$firstName;
661
        } elseif ($firstName && $lastName) {
662
            $fullName = $firstName.' '.$lastName;
663
        } elseif ($firstName) {
664
            $fullName = $firstName;
665
        } elseif ($lastName) {
666
            $fullName = $lastName;
667
        }
668
669
        return $fullName;
670
    }
671
672
    /**
673
     * Get preferred locale.
674
     *
675
     * @return string
676
     */
677
    public function getPreferredLocale()
678
    {
679
        if (isset($this->updatedFields['preferred_locale'])) {
680
            return $this->updatedFields['preferred_locale'];
681
        }
682
683
        if (!empty($this->fields['core']['preferred_locale']['value'])) {
684
            return $this->fields['core']['preferred_locale']['value'];
685
        }
686
687
        return '';
688
    }
689
690
    /**
691
     * Get the primary identifier for the lead.
692
     *
693
     * @param bool $lastFirst
694
     *
695
     * @return string
696
     */
697
    public function getPrimaryIdentifier($lastFirst = false)
698
    {
699
        if ($name = $this->getName($lastFirst)) {
700
            return $name;
701
        } elseif ($this->getCompany()) {
702
            return $this->getCompany();
703
        } elseif ($this->getEmail()) {
704
            return $this->getEmail();
705
        } elseif ($socialIdentity = $this->getFirstSocialIdentity()) {
706
            return $socialIdentity;
707
        } elseif (count($ips = $this->getIpAddresses())) {
708
            return $ips->first()->getIpAddress();
709
        } else {
710
            return 'mautic.lead.lead.anonymous';
711
        }
712
    }
713
714
    /**
715
     * Get the secondary identifier for the lead; mainly company.
716
     *
717
     * @return string
718
     */
719
    public function getSecondaryIdentifier()
720
    {
721
        if (!$this->getCompany()) {
722
            return $this->getCompany();
723
        }
724
725
        return '';
726
    }
727
728
    /**
729
     * Get the location for the lead.
730
     *
731
     * @return string
732
     */
733
    public function getLocation()
734
    {
735
        $location = '';
736
737
        if ($this->getCity()) {
738
            $location .= $this->getCity().', ';
739
        }
740
741
        if ($this->getState()) {
742
            $location .= $this->getState().', ';
743
        }
744
745
        if ($this->getCountry()) {
746
            $location .= $this->getCountry().', ';
747
        }
748
749
        return rtrim($location, ', ');
750
    }
751
752
    /**
753
     * Point changes are tracked and will be persisted as a direct DB query to avoid PHP memory overwrites with concurrent requests
754
     * The risk in this is that the $changes['points'] may not be accurate but at least no points are lost.
755
     *
756
     * @param int    $points
757
     * @param string $operator
758
     *
759
     * @return Lead
760
     */
761
    public function adjustPoints($points, $operator = self::POINTS_ADD)
762
    {
763
        if (!$points = (int) $points) {
764
            return $this;
765
        }
766
767
        // Use $updatedPoints in an attempt to keep track in the $changes log although this may not be accurate if the DB updates the points rather
768
        // than PHP memory
769
        if (null == $this->updatedPoints) {
770
            $this->updatedPoints = $this->points;
771
        }
772
        $oldPoints = $this->updatedPoints;
773
774
        switch ($operator) {
775
            case self::POINTS_ADD:
776
                $this->updatedPoints += $points;
777
                $operator = '+';
778
                break;
779
            case self::POINTS_SUBTRACT:
780
                $this->updatedPoints -= $points;
781
                $operator = '-';
782
                break;
783
            case self::POINTS_MULTIPLY:
784
                $this->updatedPoints *= $points;
785
                $operator = '*';
786
                break;
787
            case self::POINTS_DIVIDE:
788
                $this->updatedPoints /= $points;
789
                $operator = '/';
790
                break;
791
            default:
792
                throw new \UnexpectedValueException('Invalid operator');
793
        }
794
795
        // Keep track of point changes to make a direct DB query
796
        // Ignoring Aunt Sally here (PEMDAS)
797
        if (!isset($this->pointChanges[$operator])) {
798
            $this->pointChanges[$operator] = 0;
799
        }
800
        $this->pointChanges[$operator] += $points;
801
802
        $this->isChanged('points', (int) $this->updatedPoints, (int) $oldPoints);
803
804
        return $this;
805
    }
806
807
    /**
808
     * @return array
809
     */
810
    public function getPointChanges()
811
    {
812
        return $this->pointChanges;
813
    }
814
815
    /**
816
     * Set points.
817
     *
818
     * @param int $points
819
     *
820
     * @return Lead
821
     */
822
    public function setPoints($points)
823
    {
824
        $this->isChanged('points', $points);
825
        $this->points = (int) $points;
826
827
        // Something is setting points directly so reset points updated by database
828
        $this->resetPointChanges();
829
830
        return $this;
831
    }
832
833
    /**
834
     * Get points.
835
     *
836
     * @return int
837
     */
838
    public function getPoints()
839
    {
840
        if (null !== $this->actualPoints) {
841
            return $this->actualPoints;
842
        } elseif (null !== $this->updatedPoints) {
843
            return $this->updatedPoints;
844
        }
845
846
        return $this->points;
847
    }
848
849
    /**
850
     * Set by the repository method when points are updated and requeried directly on the DB side.
851
     *
852
     * @param $points
853
     */
854
    public function setActualPoints($points)
855
    {
856
        $this->actualPoints = (int) $points;
857
        $this->pointChanges = [];
858
    }
859
860
    /**
861
     * Reset point changes.
862
     *
863
     * @return $this
864
     */
865
    public function resetPointChanges()
866
    {
867
        $this->actualPoints  = null;
868
        $this->pointChanges  = [];
869
        $this->updatedPoints = null;
870
871
        return $this;
872
    }
873
874
    /**
875
     * Creates a points change entry.
876
     *
877
     * @param $type
878
     * @param $name
879
     * @param $action
880
     * @param $pointChanges
881
     */
882
    public function addPointsChangeLogEntry($type, $name, $action, $pointChanges, IpAddress $ip)
883
    {
884
        if (0 === $pointChanges) {
885
            // No need to record no change
886
            return;
887
        }
888
889
        // Create a new points change event
890
        $event = new PointsChangeLog();
891
        $event->setType($type);
892
        $event->setEventName($name);
893
        $event->setActionName($action);
894
        $event->setDateAdded(new \DateTime());
895
        $event->setDelta($pointChanges);
896
        $event->setIpAddress($ip);
897
        $event->setLead($this);
898
        $this->addPointsChangeLog($event);
899
    }
900
901
    /**
902
     * Add pointsChangeLog.
903
     *
904
     * @return Lead
905
     */
906
    public function addPointsChangeLog(PointsChangeLog $pointsChangeLog)
907
    {
908
        $this->pointsChangeLog[] = $pointsChangeLog;
909
910
        return $this;
911
    }
912
913
    /**
914
     * Creates a points change entry.
915
     *
916
     * @param $stage
917
     * @param $name
918
     * @param $action
919
     */
920
    public function stageChangeLogEntry($stage, $name, $action)
921
    {
922
        //create a new points change event
923
        $event = new StagesChangeLog();
924
        $event->setStage($stage);
925
        $event->setEventName($name);
926
        $event->setActionName($action);
927
        $event->setDateAdded(new \DateTime());
928
        $event->setLead($this);
929
        $this->stageChangeLog($event);
930
    }
931
932
    /**
933
     * Add StagesChangeLog.
934
     *
935
     * @return Lead
936
     */
937
    public function stageChangeLog(StagesChangeLog $stageChangeLog)
938
    {
939
        $this->stageChangeLog[] = $stageChangeLog;
940
941
        return $this;
942
    }
943
944
    /**
945
     * @return StagesChangeLog
946
     */
947
    public function getStageChangeLog()
948
    {
949
        return $this->stageChangeLog;
950
    }
951
952
    /**
953
     * Remove pointsChangeLog.
954
     */
955
    public function removePointsChangeLog(PointsChangeLog $pointsChangeLog)
956
    {
957
        $this->pointsChangeLog->removeElement($pointsChangeLog);
958
    }
959
960
    /**
961
     * Get pointsChangeLog.
962
     *
963
     * @return \Doctrine\Common\Collections\Collection
964
     */
965
    public function getPointsChangeLog()
966
    {
967
        return $this->pointsChangeLog;
968
    }
969
970
    /**
971
     * @param      $type
972
     * @param      $name
973
     * @param      $action
974
     * @param null $company
975
     */
976
    public function addCompanyChangeLogEntry($type, $name, $action, $company = null)
977
    {
978
        if (!$company) {
0 ignored issues
show
$company is of type null, thus it always evaluated to false.
Loading history...
979
            // No need to record a null delta
980
            return;
981
        }
982
983
        // Create a new company change event
984
        $event = new CompanyChangeLog();
985
        $event->setType($type);
986
        $event->setEventName($name);
987
        $event->setActionName($action);
988
        $event->setDateAdded(new \DateTime());
989
        $event->setCompany($company);
990
        $event->setLead($this);
991
        $this->addCompanyChangeLog($event);
992
    }
993
994
    /**
995
     * Add Company ChangeLog.
996
     *
997
     * @return Lead
998
     */
999
    public function addCompanyChangeLog(CompanyChangeLog $companyChangeLog)
1000
    {
1001
        $this->companyChangeLog[] = $companyChangeLog;
1002
1003
        return $this;
1004
    }
1005
1006
    /**
1007
     * @return CompanyChangeLog
1008
     */
1009
    public function getCompanyChangeLog()
1010
    {
1011
        return $this->companyChangeLog;
1012
    }
1013
1014
    /**
1015
     * @param      $identifier
1016
     * @param bool $enabled
1017
     * @param bool $mobile
1018
     *
1019
     * @return $this
1020
     */
1021
    public function addPushIDEntry($identifier, $enabled = true, $mobile = false)
1022
    {
1023
        $entity = new PushID();
1024
1025
        /** @var PushID $id */
1026
        foreach ($this->pushIds as $id) {
1027
            if ($id->getPushID() === $identifier) {
1028
                if ($id->isEnabled() === $enabled) {
1029
                    return $this;
1030
                } else {
1031
                    $entity = $id;
1032
                    $this->removePushID($id);
1033
                }
1034
            }
1035
        }
1036
1037
        $entity->setPushID($identifier);
1038
        $entity->setLead($this);
1039
        $entity->setEnabled($enabled);
1040
        $entity->setMobile($mobile);
1041
1042
        $this->addPushID($entity);
1043
1044
        $this->isChanged('pushIds', $this->pushIds);
1045
1046
        return $this;
1047
    }
1048
1049
    /**
1050
     * @return $this
1051
     */
1052
    public function addPushID(PushID $pushID)
1053
    {
1054
        $this->pushIds[] = $pushID;
1055
1056
        return $this;
1057
    }
1058
1059
    public function removePushID(PushID $pushID)
1060
    {
1061
        $this->pushIds->removeElement($pushID);
1062
    }
1063
1064
    /**
1065
     * @return ArrayCollection
1066
     */
1067
    public function getPushIDs()
1068
    {
1069
        return $this->pushIds;
1070
    }
1071
1072
    /**
1073
     * @return $this
1074
     */
1075
    public function addEventLog(LeadEventLog $log)
1076
    {
1077
        $this->eventLog[] = $log;
1078
        $log->setLead($this);
1079
1080
        return $this;
1081
    }
1082
1083
    public function removeEventLog(LeadEventLog $eventLog)
1084
    {
1085
        $this->eventLog->removeElement($eventLog);
1086
    }
1087
1088
    /**
1089
     * @return $this
1090
     */
1091
    public function addDoNotContactEntry(DoNotContact $doNotContact)
1092
    {
1093
        $this->changes['dnc_channel_status'][$doNotContact->getChannel()] = [
1094
            'reason'   => $doNotContact->getReason(),
1095
            'comments' => $doNotContact->getComments(),
1096
        ];
1097
1098
        $this->doNotContact[$doNotContact->getChannel()] = $doNotContact;
1099
1100
        return $this;
1101
    }
1102
1103
    public function removeDoNotContactEntry(DoNotContact $doNotContact)
1104
    {
1105
        $this->changes['dnc_channel_status'][$doNotContact->getChannel()] = [
1106
            'reason'     => DoNotContact::IS_CONTACTABLE,
1107
            'old_reason' => $doNotContact->getReason(),
1108
            'comments'   => $doNotContact->getComments(),
1109
        ];
1110
1111
        $this->doNotContact->removeElement($doNotContact);
1112
    }
1113
1114
    /**
1115
     * @return ArrayCollection
1116
     */
1117
    public function getDoNotContact()
1118
    {
1119
        return $this->doNotContact;
1120
    }
1121
1122
    /**
1123
     * Set internal storage.
1124
     *
1125
     * @param $internal
1126
     */
1127
    public function setInternal($internal)
1128
    {
1129
        $this->internal = $internal;
1130
    }
1131
1132
    /**
1133
     * Get internal storage.
1134
     *
1135
     * @return mixed
1136
     */
1137
    public function getInternal()
1138
    {
1139
        return $this->internal;
1140
    }
1141
1142
    /**
1143
     * Set social cache.
1144
     *
1145
     * @param $cache
1146
     */
1147
    public function setSocialCache($cache)
1148
    {
1149
        $this->socialCache = $cache;
1150
    }
1151
1152
    /**
1153
     * Get social cache.
1154
     *
1155
     * @return mixed
1156
     */
1157
    public function getSocialCache()
1158
    {
1159
        return $this->socialCache;
1160
    }
1161
1162
    /**
1163
     * @return mixed
1164
     */
1165
    public function getColor()
1166
    {
1167
        return $this->color;
1168
    }
1169
1170
    /**
1171
     * @param mixed $color
1172
     */
1173
    public function setColor($color)
1174
    {
1175
        $this->color = $color;
1176
    }
1177
1178
    /**
1179
     * @return bool
1180
     */
1181
    public function isAnonymous()
1182
    {
1183
        return !($this->getName()
1184
            || $this->getFirstname()
1185
            || $this->getLastname()
1186
            || $this->getCompany()
1187
            || $this->getEmail()
1188
            || $this->getFirstSocialIdentity()
1189
        );
1190
    }
1191
1192
    /**
1193
     * @return bool
1194
     */
1195
    public function wasAnonymous()
1196
    {
1197
        return null == $this->dateIdentified && false === $this->isAnonymous();
1198
    }
1199
1200
    /**
1201
     * @return bool
1202
     */
1203
    protected function getFirstSocialIdentity()
1204
    {
1205
        if (isset($this->fields['social'])) {
1206
            foreach ($this->fields['social'] as $social) {
1207
                if (!empty($social['value'])) {
1208
                    return $social['value'];
1209
                }
1210
            }
1211
        } elseif (!empty($this->updatedFields)) {
1212
            foreach ($this->availableSocialFields as $social) {
1213
                if (!empty($this->updatedFields[$social])) {
1214
                    return $this->updatedFields[$social];
1215
                }
1216
            }
1217
        }
1218
1219
        return false;
1220
    }
1221
1222
    /**
1223
     * @return self
1224
     */
1225
    public function setManipulator(LeadManipulator $manipulator = null)
1226
    {
1227
        $this->manipulator = $manipulator;
1228
1229
        return $this;
1230
    }
1231
1232
    /**
1233
     * @return LeadManipulator|null
1234
     */
1235
    public function getManipulator()
1236
    {
1237
        return $this->manipulator;
1238
    }
1239
1240
    /**
1241
     * @return bool
1242
     */
1243
    public function isNewlyCreated()
1244
    {
1245
        return $this->newlyCreated;
1246
    }
1247
1248
    /**
1249
     * @param bool $newlyCreated Created
1250
     */
1251
    public function setNewlyCreated($newlyCreated)
1252
    {
1253
        $this->newlyCreated = $newlyCreated;
1254
    }
1255
1256
    /**
1257
     * @return mixed
1258
     */
1259
    public function getNotes()
1260
    {
1261
        return $this->notes;
1262
    }
1263
1264
    /**
1265
     * @param string $source
1266
     */
1267
    public function setPreferredProfileImage($source)
1268
    {
1269
        $this->preferredProfileImage = $source;
1270
    }
1271
1272
    /**
1273
     * @return string
1274
     */
1275
    public function getPreferredProfileImage()
1276
    {
1277
        return $this->preferredProfileImage;
1278
    }
1279
1280
    /**
1281
     * @return mixed
1282
     */
1283
    public function getDateIdentified()
1284
    {
1285
        return $this->dateIdentified;
1286
    }
1287
1288
    /**
1289
     * @param mixed $dateIdentified
1290
     */
1291
    public function setDateIdentified($dateIdentified)
1292
    {
1293
        $this->isChanged('dateIdentified', $dateIdentified);
1294
        $this->dateIdentified = $dateIdentified;
1295
    }
1296
1297
    /**
1298
     * @return mixed
1299
     */
1300
    public function getLastActive()
1301
    {
1302
        return $this->lastActive;
1303
    }
1304
1305
    /**
1306
     * @param mixed $lastActive
1307
     */
1308
    public function setLastActive($lastActive)
1309
    {
1310
        $this->changes['dateLastActive'] = [$this->lastActive, $lastActive];
1311
        $this->lastActive                = $lastActive;
1312
    }
1313
1314
    public function setAvailableSocialFields(array $availableSocialFields)
1315
    {
1316
        $this->availableSocialFields = $availableSocialFields;
1317
    }
1318
1319
    /**
1320
     * Add tag.
1321
     *
1322
     * @return Lead
1323
     */
1324
    public function addTag(Tag $tag)
1325
    {
1326
        $this->isChanged('tags', $tag);
1327
        $this->tags[$tag->getTag()] = $tag;
1328
1329
        return $this;
1330
    }
1331
1332
    /**
1333
     * Remove tag.
1334
     */
1335
    public function removeTag(Tag $tag)
1336
    {
1337
        $this->isChanged('tags', $tag->getTag());
1338
        $this->tags->removeElement($tag);
1339
    }
1340
1341
    /**
1342
     * Get tags.
1343
     *
1344
     * @return mixed
1345
     */
1346
    public function getTags()
1347
    {
1348
        return $this->tags;
1349
    }
1350
1351
    /**
1352
     * Set tags.
1353
     *
1354
     * @param $tags
1355
     *
1356
     * @return $this
1357
     */
1358
    public function setTags($tags)
1359
    {
1360
        $this->tags = $tags;
1361
1362
        return $this;
1363
    }
1364
1365
    /**
1366
     * Get utm tags.
1367
     *
1368
     * @return mixed
1369
     */
1370
    public function getUtmTags()
1371
    {
1372
        return $this->utmtags;
1373
    }
1374
1375
    /**
1376
     * Set utm tags.
1377
     *
1378
     * @param $utmTags
1379
     *
1380
     * @return $this
1381
     */
1382
    public function setUtmTags($utmTags)
1383
    {
1384
        $this->isChanged('utmtags', $utmTags);
1385
        $this->utmtags[] = $utmTags;
1386
1387
        return $this;
1388
    }
1389
1390
    public function removeUtmTagEntry(UtmTag $utmTag)
1391
    {
1392
        $this->changes['utmtags'] = ['removed', 'UtmTagID:'.$utmTag->getId()];
1393
        $this->utmtags->removeElement($utmTag);
1394
    }
1395
1396
    /**
1397
     * Set stage.
1398
     *
1399
     * @param \Mautic\StageBundle\Entity\Stage $stage
1400
     *
1401
     * @return Stage
1402
     */
1403
    public function setStage(Stage $stage = null)
1404
    {
1405
        $this->isChanged('stage', $stage);
1406
        $this->stage = $stage;
1407
1408
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Mautic\LeadBundle\Entity\Lead which is incompatible with the documented return type Mautic\StageBundle\Entity\Stage.
Loading history...
1409
    }
1410
1411
    /**
1412
     * Get stage.
1413
     *
1414
     * @return \Mautic\StageBundle\Entity\Stage
1415
     */
1416
    public function getStage()
1417
    {
1418
        return $this->stage;
1419
    }
1420
1421
    /**
1422
     * Set frequency rules.
1423
     *
1424
     * @param FrequencyRule[] $frequencyRules
1425
     *
1426
     * @return Lead
1427
     */
1428
    public function setFrequencyRules($frequencyRules)
1429
    {
1430
        $this->frequencyRules = $frequencyRules;
1431
1432
        return $this;
1433
    }
1434
1435
    /**
1436
     * Get frequency rules.
1437
     *
1438
     * @return ArrayCollection
1439
     */
1440
    public function getFrequencyRules()
1441
    {
1442
        return $this->frequencyRules;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->frequencyRules returns the type Mautic\LeadBundle\Entity\FrequencyRule[] which is incompatible with the documented return type Doctrine\Common\Collections\ArrayCollection.
Loading history...
1443
    }
1444
1445
    /**
1446
     * Remove frequencyRule.
1447
     */
1448
    public function removeFrequencyRule(FrequencyRule $frequencyRule)
1449
    {
1450
        $this->isChanged('frequencyRules', $frequencyRule->getId(), false);
1451
        $this->frequencyRules->removeElement($frequencyRule);
1452
    }
1453
1454
    /**
1455
     * Add frequency rule.
1456
     */
1457
    public function addFrequencyRule(FrequencyRule $frequencyRule)
1458
    {
1459
        $this->isChanged('frequencyRules', $frequencyRule, false);
1460
        $this->frequencyRules[] = $frequencyRule;
1461
    }
1462
1463
    /**
1464
     * Get attribution value.
1465
     *
1466
     * @return bool
1467
     */
1468
    public function getAttribution()
1469
    {
1470
        return (float) $this->getFieldValue('attribution');
1471
    }
1472
1473
    /**
1474
     * If there is an attribution amount but no date, insert today's date.
1475
     */
1476
    public function checkAttributionDate()
1477
    {
1478
        $attribution     = $this->getFieldValue('attribution');
1479
        $attributionDate = $this->getFieldValue('attribution_date');
1480
1481
        if (!empty($attribution) && empty($attributionDate)) {
1482
            $this->addUpdatedField('attribution_date', (new \DateTime())->format('Y-m-d'));
1483
        } elseif (empty($attribution) && !empty($attributionDate)) {
1484
            $this->addUpdatedField('attribution_date', null);
1485
        }
1486
    }
1487
1488
    /**
1489
     * Set date identified.
1490
     */
1491
    public function checkDateIdentified()
1492
    {
1493
        if ($this->wasAnonymous()) {
1494
            $this->dateIdentified            = new \DateTime();
1495
            $this->changes['dateIdentified'] = ['', $this->dateIdentified];
1496
        }
1497
    }
1498
1499
    /**
1500
     * Set date added if not already set.
1501
     */
1502
    public function checkDateAdded()
1503
    {
1504
        if (null === $this->getDateAdded()) {
1505
            $this->setDateAdded(new \DateTime());
1506
        }
1507
    }
1508
1509
    /**
1510
     * @return mixed
1511
     */
1512
    public function getPrimaryCompany()
1513
    {
1514
        return $this->primaryCompany;
1515
    }
1516
1517
    /**
1518
     * @param mixed $primaryCompany
1519
     *
1520
     * @return Lead
1521
     */
1522
    public function setPrimaryCompany($primaryCompany)
1523
    {
1524
        $this->primaryCompany = $primaryCompany;
1525
1526
        return $this;
1527
    }
1528
1529
    /**
1530
     * @return mixed
1531
     */
1532
    public function getTitle()
1533
    {
1534
        return $this->title;
1535
    }
1536
1537
    /**
1538
     * @param mixed $title
1539
     *
1540
     * @return Lead
1541
     */
1542
    public function setTitle($title)
1543
    {
1544
        $this->isChanged('title', $title);
1545
        $this->title = $title;
1546
1547
        return $this;
1548
    }
1549
1550
    /**
1551
     * @return mixed
1552
     */
1553
    public function getFirstname()
1554
    {
1555
        return $this->firstname;
1556
    }
1557
1558
    /**
1559
     * @param mixed $firstname
1560
     *
1561
     * @return Lead
1562
     */
1563
    public function setFirstname($firstname)
1564
    {
1565
        $this->isChanged('firstname', $firstname);
1566
        $this->firstname = $firstname;
1567
1568
        return $this;
1569
    }
1570
1571
    /**
1572
     * @return mixed
1573
     */
1574
    public function getLastname()
1575
    {
1576
        return $this->lastname;
1577
    }
1578
1579
    /**
1580
     * @param mixed $lastname
1581
     *
1582
     * @return Lead
1583
     */
1584
    public function setLastname($lastname)
1585
    {
1586
        $this->isChanged('lastname', $lastname);
1587
        $this->lastname = $lastname;
1588
1589
        return $this;
1590
    }
1591
1592
    /**
1593
     * @return mixed
1594
     */
1595
    public function getPosition()
1596
    {
1597
        return $this->position;
1598
    }
1599
1600
    /**
1601
     * @param mixed $position
1602
     *
1603
     * @return Lead
1604
     */
1605
    public function setPosition($position)
1606
    {
1607
        $this->isChanged('position', $position);
1608
        $this->position = $position;
1609
1610
        return $this;
1611
    }
1612
1613
    /**
1614
     * @return mixed
1615
     */
1616
    public function getPhone()
1617
    {
1618
        return $this->phone;
1619
    }
1620
1621
    /**
1622
     * @param mixed $phone
1623
     *
1624
     * @return Lead
1625
     */
1626
    public function setPhone($phone)
1627
    {
1628
        $this->isChanged('phone', $phone);
1629
        $this->phone = $phone;
1630
1631
        return $this;
1632
    }
1633
1634
    /**
1635
     * @return mixed
1636
     */
1637
    public function getMobile()
1638
    {
1639
        return $this->mobile;
1640
    }
1641
1642
    /**
1643
     * @param mixed $mobile
1644
     *
1645
     * @return Lead
1646
     */
1647
    public function setMobile($mobile)
1648
    {
1649
        $this->isChanged('mobile', $mobile);
1650
        $this->mobile = $mobile;
1651
1652
        return $this;
1653
    }
1654
1655
    /**
1656
     * @return string|null
1657
     */
1658
    public function getLeadPhoneNumber()
1659
    {
1660
        return $this->getMobile() ?: $this->getPhone();
1661
    }
1662
1663
    /**
1664
     * @return mixed
1665
     */
1666
    public function getAddress1()
1667
    {
1668
        return $this->address1;
1669
    }
1670
1671
    /**
1672
     * @param mixed $address1
1673
     *
1674
     * @return Lead
1675
     */
1676
    public function setAddress1($address1)
1677
    {
1678
        $this->isChanged('address1', $address1);
1679
        $this->address1 = $address1;
1680
1681
        return $this;
1682
    }
1683
1684
    /**
1685
     * @return mixed
1686
     */
1687
    public function getAddress2()
1688
    {
1689
        return $this->address2;
1690
    }
1691
1692
    /**
1693
     * @param mixed $address2
1694
     *
1695
     * @return Lead
1696
     */
1697
    public function setAddress2($address2)
1698
    {
1699
        $this->isChanged('address2', $address2);
1700
        $this->address2 = $address2;
1701
1702
        return $this;
1703
    }
1704
1705
    /**
1706
     * @return mixed
1707
     */
1708
    public function getCity()
1709
    {
1710
        return $this->city;
1711
    }
1712
1713
    /**
1714
     * @param mixed $city
1715
     *
1716
     * @return Lead
1717
     */
1718
    public function setCity($city)
1719
    {
1720
        $this->isChanged('city', $city);
1721
        $this->city = $city;
1722
1723
        return $this;
1724
    }
1725
1726
    /**
1727
     * @return mixed
1728
     */
1729
    public function getState()
1730
    {
1731
        return $this->state;
1732
    }
1733
1734
    /**
1735
     * @param mixed $state
1736
     *
1737
     * @return Lead
1738
     */
1739
    public function setState($state)
1740
    {
1741
        $this->isChanged('state', $state);
1742
        $this->state = $state;
1743
1744
        return $this;
1745
    }
1746
1747
    /**
1748
     * @return mixed
1749
     */
1750
    public function getZipcode()
1751
    {
1752
        return $this->zipcode;
1753
    }
1754
1755
    /**
1756
     * @param mixed $zipcode
1757
     *
1758
     * @return Lead
1759
     */
1760
    public function setZipcode($zipcode)
1761
    {
1762
        $this->isChanged('zipcode', $zipcode);
1763
        $this->zipcode = $zipcode;
1764
1765
        return $this;
1766
    }
1767
1768
    /**
1769
     * @return string
1770
     */
1771
    public function getTimezone()
1772
    {
1773
        return $this->timezone;
1774
    }
1775
1776
    /**
1777
     * @param string $timezone
1778
     *
1779
     * @return Lead
1780
     */
1781
    public function setTimezone($timezone)
1782
    {
1783
        $this->isChanged('timezone', $timezone);
1784
        $this->timezone = $timezone;
1785
1786
        return $this;
1787
    }
1788
1789
    /**
1790
     * @return mixed
1791
     */
1792
    public function getCountry()
1793
    {
1794
        return $this->country;
1795
    }
1796
1797
    /**
1798
     * @param mixed $country
1799
     *
1800
     * @return Lead
1801
     */
1802
    public function setCountry($country)
1803
    {
1804
        $this->isChanged('country', $country);
1805
        $this->country = $country;
1806
1807
        return $this;
1808
    }
1809
1810
    /**
1811
     * @return mixed
1812
     */
1813
    public function getCompany()
1814
    {
1815
        return $this->company;
1816
    }
1817
1818
    /**
1819
     * @param mixed $company
1820
     *
1821
     * @return Lead
1822
     */
1823
    public function setCompany($company)
1824
    {
1825
        $this->isChanged('company', $company);
1826
        $this->company = $company;
1827
1828
        return $this;
1829
    }
1830
1831
    /**
1832
     * @return mixed
1833
     */
1834
    public function getEmail()
1835
    {
1836
        return $this->email;
1837
    }
1838
1839
    /**
1840
     * @param mixed $email
1841
     *
1842
     * @return Lead
1843
     */
1844
    public function setEmail($email)
1845
    {
1846
        $this->isChanged('email', $email);
1847
        $this->email = $email;
1848
1849
        return $this;
1850
    }
1851
1852
    /**
1853
     * Returns array of rules with preferred channels first.
1854
     *
1855
     * @return mixed
1856
     */
1857
    public function getChannelRules()
1858
    {
1859
        if (null === $this->channelRules) {
1860
            $frequencyRules = $this->getFrequencyRules()->toArray();
1861
            $dnc            = $this->getDoNotContact();
1862
            $dncChannels    = [];
1863
            /** @var DoNotContact $record */
1864
            foreach ($dnc as $record) {
1865
                $dncChannels[$record->getChannel()] = $record->getReason();
1866
            }
1867
1868
            $this->channelRules = self::generateChannelRules($frequencyRules, $dncChannels);
1869
        }
1870
1871
        return $this->channelRules;
1872
    }
1873
1874
    /**
1875
     * @return $this
1876
     */
1877
    public function setChannelRules(array $rules)
1878
    {
1879
        $this->channelRules = $rules;
1880
1881
        return $this;
1882
    }
1883
1884
    /**
1885
     * Used mostly when batching to generate preferred channels without hydrating associations one at a time.
1886
     */
1887
    public static function generateChannelRules(array $frequencyRules, array $dncRules)
1888
    {
1889
        $rules             = [];
1890
        $dncFrequencyRules = [];
1891
        foreach ($frequencyRules as $rule) {
1892
            if ($rule instanceof FrequencyRule) {
1893
                $ruleArray = [
1894
                    'channel'           => $rule->getChannel(),
1895
                    'pause_from_date'   => $rule->getPauseFromDate(),
1896
                    'pause_to_date'     => $rule->getPauseToDate(),
1897
                    'preferred_channel' => $rule->getPreferredChannel(),
1898
                    'frequency_time'    => $rule->getFrequencyTime(),
1899
                    'frequency_number'  => $rule->getFrequencyNumber(),
1900
                ];
1901
1902
                if (array_key_exists($rule->getChannel(), $dncRules)) {
1903
                    $dncFrequencyRules[$rule->getChannel()] = $ruleArray;
1904
                } else {
1905
                    $rules[$rule->getChannel()] = $ruleArray;
1906
                }
1907
            } else {
1908
                // Already an array
1909
                break;
1910
            }
1911
        }
1912
1913
        if (count($rules)) {
1914
            $frequencyRules = $rules;
1915
        }
1916
1917
        /* @var FrequencyRule $rule */
1918
        usort(
1919
            $frequencyRules,
1920
            function ($a, $b) {
1921
                if ($a['pause_from_date'] && $a['pause_to_date']) {
1922
                    $now = new \DateTime();
1923
                    if ($now >= $a['pause_from_date'] && $now <= $a['pause_to_date']) {
1924
                        // A is paused so give lower preference
1925
                        return 1;
1926
                    }
1927
                }
1928
1929
                if ($a['preferred_channel'] === $b['preferred_channel']) {
1930
                    if (!$a['frequency_time'] || !$b['frequency_time'] || !$a['frequency_number'] || !$b['frequency_number']) {
1931
                        return 0;
1932
                    }
1933
1934
                    // Order by which ever can be sent more frequent
1935
                    if ($a['frequency_time'] === $b['frequency_time']) {
1936
                        if ($a['frequency_number'] === $b['frequency_number']) {
1937
                            return 0;
1938
                        }
1939
1940
                        return ($a['frequency_number'] > $b['frequency_number']) ? -1 : 1;
1941
                    } else {
1942
                        $convertToMonth = function ($number, $unit) {
1943
                            switch ($unit) {
1944
                                case FrequencyRule::TIME_MONTH:
1945
                                    $number = (int) $number;
1946
                                    break;
1947
                                case FrequencyRule::TIME_WEEK:
1948
                                    $number = $number * 4;
1949
                                    break;
1950
                                case FrequencyRule::TIME_DAY:
1951
                                    $number = $number * 30;
1952
                                    break;
1953
                            }
1954
1955
                            return $number;
1956
                        };
1957
1958
                        $aFrequency = $convertToMonth($a['frequency_number'], $a['frequency_time']);
1959
                        $bFrequency = $convertToMonth($b['frequency_number'], $b['frequency_time']);
1960
1961
                        if ($aFrequency === $bFrequency) {
1962
                            return 0;
1963
                        }
1964
1965
                        return ($aFrequency > $bFrequency) ? -1 : 1;
1966
                    }
1967
                }
1968
1969
                return ($a['preferred_channel'] > $b['preferred_channel']) ? -1 : 1;
1970
            }
1971
        );
1972
1973
        $rules = [];
1974
        foreach ($frequencyRules as $rule) {
1975
            $rules[$rule['channel']] =
1976
                [
1977
                    'frequency' => $rule,
1978
                    'dnc'       => DoNotContact::IS_CONTACTABLE,
1979
                ];
1980
        }
1981
1982
        if (count($dncRules)) {
1983
            foreach ($dncRules as $channel => $reason) {
1984
                $rules[$channel] = [
1985
                    'frequency' => (isset($dncFrequencyRules[$channel])) ? $dncFrequencyRules[$channel] : null,
1986
                    'dnc'       => $reason,
1987
                ];
1988
            }
1989
        }
1990
1991
        return $rules;
1992
    }
1993
}
1994