Issues (3627)

EmailBundle/MonitoredEmail/Processor/Bounce.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\EmailBundle\MonitoredEmail\Processor;
13
14
use Mautic\CoreBundle\Helper\DateTimeHelper;
15
use Mautic\EmailBundle\Entity\Email;
16
use Mautic\EmailBundle\Entity\Stat;
17
use Mautic\EmailBundle\Entity\StatRepository;
18
use Mautic\EmailBundle\MonitoredEmail\Exception\BounceNotFound;
19
use Mautic\EmailBundle\MonitoredEmail\Message;
20
use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce\BouncedEmail;
21
use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce\Parser;
22
use Mautic\EmailBundle\MonitoredEmail\Search\ContactFinder;
23
use Mautic\EmailBundle\Swiftmailer\Transport\BounceProcessorInterface;
24
use Mautic\LeadBundle\Model\DoNotContact;
25
use Mautic\LeadBundle\Model\LeadModel;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\Translation\TranslatorInterface;
28
29
class Bounce implements ProcessorInterface
30
{
31
    /**
32
     * @var \Swift_Transport
33
     */
34
    protected $transport;
35
36
    /**
37
     * @var ContactFinder
38
     */
39
    protected $contactFinder;
40
41
    /**
42
     * @var StatRepository
43
     */
44
    protected $statRepository;
45
46
    /**
47
     * @var LeadModel
48
     */
49
    protected $leadModel;
50
51
    /**
52
     * @var TranslatorInterface
53
     */
54
    protected $translator;
55
56
    /**
57
     * @var string
58
     */
59
    protected $bouncerAddress;
60
61
    /**
62
     * @var LoggerInterface
63
     */
64
    protected $logger;
65
66
    /**
67
     * @var Message
68
     */
69
    protected $message;
70
71
    /**
72
     * @var DoNotContact
73
     */
74
    protected $doNotContact;
75
76
    /**
77
     * Bounce constructor.
78
     */
79
    public function __construct(
80
        \Swift_Transport $transport,
81
        ContactFinder $contactFinder,
82
        StatRepository $statRepository,
83
        LeadModel $leadModel,
84
        TranslatorInterface $translator,
85
        LoggerInterface $logger,
86
        DoNotContact $doNotContact
87
    ) {
88
        $this->transport      = $transport;
89
        $this->contactFinder  = $contactFinder;
90
        $this->statRepository = $statRepository;
91
        $this->leadModel      = $leadModel;
92
        $this->translator     = $translator;
93
        $this->logger         = $logger;
94
        $this->doNotContact   = $doNotContact;
95
    }
96
97
    /**
98
     * @return bool
99
     */
100
    public function process(Message $message)
101
    {
102
        $this->message = $message;
103
        $bounce        = false;
104
105
        $this->logger->debug('MONITORED EMAIL: Processing message ID '.$this->message->id.' for a bounce');
106
107
        // Does the transport have special handling such as Amazon SNS?
108
        if ($this->transport instanceof BounceProcessorInterface) {
109
            try {
110
                $bounce = $this->transport->processBounce($this->message);
111
            } catch (BounceNotFound $exception) {
112
                // Attempt to parse a bounce the standard way
113
            }
114
        }
115
116
        if (!$bounce) {
117
            try {
118
                $bounce = (new Parser($this->message))->parse();
119
            } catch (BounceNotFound $exception) {
120
                return false;
121
            }
122
        }
123
124
        $searchResult = $this->contactFinder->find($bounce->getContactEmail(), $bounce->getBounceAddress());
125
        if (!$contacts = $searchResult->getContacts()) {
126
            // No contacts found so bail
127
            return false;
128
        }
129
130
        $stat    = $searchResult->getStat();
131
        $channel = 'email';
132
        if ($stat) {
133
            // Update stat entry
134
            $this->updateStat($stat, $bounce);
135
136
            if ($stat->getEmail() instanceof Email) {
137
                // We know the email ID so set it to append to the the DNC record
138
                $channel = ['email' => $stat->getEmail()->getId()];
139
            }
140
        }
141
142
        $comments = $this->translator->trans('mautic.email.bounce.reason.'.$bounce->getRuleCategory());
143
        foreach ($contacts as $contact) {
144
            $this->doNotContact->addDncForContact($contact->getId(), $channel, \Mautic\LeadBundle\Entity\DoNotContact::BOUNCED, $comments);
145
        }
146
147
        return true;
148
    }
149
150
    protected function updateStat(Stat $stat, BouncedEmail $bouncedEmail)
151
    {
152
        $dtHelper    = new DateTimeHelper();
153
        $openDetails = $stat->getOpenDetails();
154
155
        if (!isset($openDetails['bounces'])) {
156
            $openDetails['bounces'] = [];
157
        }
158
159
        $openDetails['bounces'][] = [
160
            'datetime' => $dtHelper->toUtcString(),
161
            'reason'   => $bouncedEmail->getRuleCategory(),
162
            'code'     => $bouncedEmail->getRuleNumber(),
163
            'type'     => $bouncedEmail->getType(),
164
        ];
165
166
        $stat->setOpenDetails($openDetails);
167
168
        $retryCount = $stat->getRetryCount();
169
        ++$retryCount;
170
        $stat->setRetryCount($retryCount);
171
172
        if ($fail = $bouncedEmail->isFinal() || $retryCount >= 5) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $fail = ($bouncedEmail->...() || $retryCount >= 5), Probably Intended Meaning: ($fail = $bouncedEmail->...()) || $retryCount >= 5
Loading history...
173
            $stat->setIsFailed(true);
174
        }
175
176
        $this->statRepository->saveEntity($stat);
177
    }
178
}
179