Issues (3627)

app/bundles/LeadBundle/Model/DoNotContact.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2017 Mautic Contributors. All rights reserved
5
 * @author      Mautic, Inc.
6
 *
7
 * @link        https://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 Mautic\LeadBundle\Entity\DoNotContact as DNC;
15
use Mautic\LeadBundle\Entity\DoNotContactRepository;
16
use Mautic\LeadBundle\Entity\Lead;
17
18
class DoNotContact
19
{
20
    /**
21
     * @var LeadModel
22
     */
23
    protected $leadModel;
24
25
    /**
26
     * @var DoNotContactRepository
27
     */
28
    protected $dncRepo;
29
30
    /**
31
     * DoNotContact constructor.
32
     */
33
    public function __construct(LeadModel $leadModel, DoNotContactRepository $dncRepo)
34
    {
35
        $this->leadModel = $leadModel;
36
        $this->dncRepo   = $dncRepo;
37
    }
38
39
    /**
40
     * Remove a Lead's DNC entry based on channel.
41
     *
42
     * @param int       $contactId
43
     * @param string    $channel
44
     * @param bool|true $persist
45
     * @param int|null  $reason
46
     *
47
     * @return bool
48
     */
49
    public function removeDncForContact($contactId, $channel, $persist = true, $reason = null)
50
    {
51
        $contact = $this->leadModel->getEntity($contactId);
52
53
        /** @var DNC $dnc */
54
        foreach ($contact->getDoNotContact() as $dnc) {
55
            if ($dnc->getChannel() === $channel) {
56
                // Skip if reason doesn't match
57
                // Some integrations (Sugar CRM) can use both reasons (unsubscribed, bounced)
58
                if ($reason && $dnc->getReason() != $reason) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $reason 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...
59
                    continue;
60
                }
61
                $contact->removeDoNotContactEntry($dnc);
62
63
                if ($persist) {
64
                    $this->leadModel->saveEntity($contact);
65
                }
66
67
                return true;
68
            }
69
        }
70
71
        return false;
72
    }
73
74
    /**
75
     * Create a DNC entry for a lead.
76
     *
77
     * @param int          $contactId
78
     * @param string|array $channel                  If an array with an ID, use the structure ['email' => 123]
79
     * @param string       $comments
80
     * @param int          $reason                   Must be a class constant from the DoNotContact class
81
     * @param bool         $persist
82
     * @param bool         $checkCurrentStatus
83
     * @param bool         $allowUnsubscribeOverride
84
     *
85
     * @return bool|DNC If a DNC entry is added or updated, returns the DoNotContact object. If a DNC is already present
86
     *                  and has the specified reason, nothing is done and this returns false
87
     */
88
    public function addDncForContact(
89
        $contactId,
90
        $channel,
91
        $reason = DNC::BOUNCED,
92
        $comments = '',
93
        $persist = true,
94
        $checkCurrentStatus = true,
95
        $allowUnsubscribeOverride = false
96
    ) {
97
        $dnc     = false;
98
        $contact = $this->leadModel->getEntity($contactId);
99
100
        if (null === $contact) {
101
            // Contact not found, nothing to do
102
            return false;
103
        }
104
105
        // if !$checkCurrentStatus, assume is contactable due to already being validated
106
        $isContactable = ($checkCurrentStatus) ? $this->isContactable($contact, $channel) : DNC::IS_CONTACTABLE;
107
108
        // If they don't have a DNC entry yet
109
        if (DNC::IS_CONTACTABLE === $isContactable) {
110
            $dnc = $this->createDncRecord($contact, $channel, $reason, $comments);
111
        } elseif ($isContactable !== $reason) {
112
            // Or if the given reason is different than the stated reason
113
114
            /** @var DNC $dnc */
115
            foreach ($contact->getDoNotContact() as $dnc) {
116
                // Only update if the contact did not unsubscribe themselves or if the code forces it
117
                $allowOverride = ($allowUnsubscribeOverride || DNC::UNSUBSCRIBED !== $dnc->getReason());
118
119
                // Only update if the contact did not unsubscribe themselves
120
                if ($allowOverride && $dnc->getChannel() === $channel) {
121
                    // Note the outdated entry for listeners
122
                    $contact->removeDoNotContactEntry($dnc);
123
124
                    // Update the entry with the latest
125
                    $this->updateDncRecord($dnc, $contact, $channel, $reason, $comments);
126
127
                    break;
128
                }
129
            }
130
        }
131
132
        if ($dnc && $persist) {
133
            // Use model saveEntity to trigger events for DNC change
134
            $this->leadModel->saveEntity($contact);
135
        }
136
137
        return $dnc;
138
    }
139
140
    /**
141
     * @param string $channel
142
     *
143
     * @return int
144
     *
145
     * @see \Mautic\LeadBundle\Entity\DoNotContact This method can return boolean false, so be
146
     *                                             sure to always compare the return value against
147
     *                                             the class constants of DoNotContact
148
     */
149
    public function isContactable(Lead $contact, $channel)
150
    {
151
        if (is_array($channel)) {
152
            $channel = key($channel);
153
        }
154
155
        /** @var \Mautic\LeadBundle\Entity\DoNotContact[] $entries */
156
        $dncEntries = $this->dncRepo->getEntriesByLeadAndChannel($contact, $channel);
157
158
        // If the lead has no entries in the DNC table, we're good to go
159
        if (empty($dncEntries)) {
160
            return DNC::IS_CONTACTABLE;
161
        }
162
163
        foreach ($dncEntries as $dnc) {
164
            if (DNC::IS_CONTACTABLE !== $dnc->getReason()) {
165
                return $dnc->getReason();
166
            }
167
        }
168
169
        return DNC::IS_CONTACTABLE;
170
    }
171
172
    /**
173
     * @param      $channel
174
     * @param      $reason
175
     * @param null $comments
176
     *
177
     * @return DNC
178
     */
179
    public function createDncRecord(Lead $contact, $channel, $reason, $comments = null)
180
    {
181
        $dnc = new DNC();
182
183
        if (is_array($channel)) {
184
            $channelId = reset($channel);
185
            $channel   = key($channel);
186
187
            $dnc->setChannelId((int) $channelId);
188
        }
189
190
        $dnc->setChannel($channel);
191
        $dnc->setReason($reason);
192
        $dnc->setLead($contact);
193
        $dnc->setDateAdded(new \DateTime());
194
        $dnc->setComments($comments);
195
196
        $contact->addDoNotContactEntry($dnc);
197
198
        return $dnc;
199
    }
200
201
    /**
202
     * @param      $channel
203
     * @param      $reason
204
     * @param null $comments
205
     */
206
    public function updateDncRecord(DNC $dnc, Lead $contact, $channel, $reason, $comments = null)
207
    {
208
        // Update the DNC entry
209
        $dnc->setChannel($channel);
210
        $dnc->setReason($reason);
211
        $dnc->setLead($contact);
212
        $dnc->setDateAdded(new \DateTime());
213
        $dnc->setComments($comments);
214
215
        // Re-add the entry to the lead
216
        $contact->addDoNotContactEntry($dnc);
217
    }
218
219
    /**
220
     * Clear DoNotContact entities from Doctrine UnitOfWork.
221
     */
222
    public function clearEntities()
223
    {
224
        $this->dncRepo->clear();
225
    }
226
227
    /**
228
     * @return DoNotContactRepository
229
     */
230
    public function getDncRepo()
231
    {
232
        return $this->dncRepo;
233
    }
234
}
235