1 | <?php |
||
2 | |||
3 | /* |
||
4 | * @copyright 2020 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\EmailBundle\Swiftmailer\Amazon; |
||
13 | |||
14 | use Joomla\Http\Exception\UnexpectedResponseException; |
||
15 | use Joomla\Http\Http; |
||
16 | use Mautic\EmailBundle\Model\TransportCallback; |
||
17 | use Mautic\EmailBundle\MonitoredEmail\Exception\BounceNotFound; |
||
18 | use Mautic\EmailBundle\MonitoredEmail\Exception\UnsubscriptionNotFound; |
||
19 | use Mautic\EmailBundle\MonitoredEmail\Message; |
||
20 | use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce\BouncedEmail; |
||
21 | use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce\Definition\Category; |
||
22 | use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce\Definition\Type; |
||
23 | use Mautic\EmailBundle\MonitoredEmail\Processor\Unsubscription\UnsubscribedEmail; |
||
24 | use Mautic\LeadBundle\Entity\DoNotContact; |
||
25 | use Psr\Log\LoggerInterface; |
||
26 | use Symfony\Component\HttpFoundation\Request; |
||
27 | use Symfony\Component\HttpKernel\Exception\HttpException; |
||
28 | use Symfony\Component\Translation\TranslatorInterface; |
||
29 | |||
30 | class AmazonCallback |
||
31 | { |
||
32 | /** |
||
33 | * From address for SNS email. |
||
34 | */ |
||
35 | const SNS_ADDRESS = '[email protected]'; |
||
36 | |||
37 | /** |
||
38 | * @var TranslatorInterface |
||
39 | */ |
||
40 | private $translator; |
||
41 | |||
42 | /** |
||
43 | * @var LoggerInterface |
||
44 | */ |
||
45 | private $logger; |
||
46 | |||
47 | /** |
||
48 | * @var Http |
||
49 | */ |
||
50 | private $httpClient; |
||
51 | |||
52 | /** |
||
53 | * @var TransportCallback |
||
54 | */ |
||
55 | private $transportCallback; |
||
56 | |||
57 | public function __construct(TranslatorInterface $translator, LoggerInterface $logger, Http $httpClient, TransportCallback $transportCallback) |
||
58 | { |
||
59 | $this->translator = $translator; |
||
60 | $this->logger = $logger; |
||
61 | $this->transportCallback = $transportCallback; |
||
62 | $this->httpClient = $httpClient; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Handle bounces & complaints from Amazon. |
||
67 | * |
||
68 | * @return array |
||
69 | */ |
||
70 | public function processCallbackRequest(Request $request) |
||
71 | { |
||
72 | $this->logger->debug('Receiving webhook from Amazon'); |
||
73 | |||
74 | $payload = json_decode($request->getContent(), true); |
||
75 | |||
76 | if (0 !== json_last_error()) { |
||
77 | throw new HttpException(400, 'AmazonCallback: Invalid JSON Payload'); |
||
78 | } |
||
79 | |||
80 | if (!isset($payload['Type']) && !isset($payload['eventType'])) { |
||
81 | throw new HttpException(400, "Key 'Type' not found in payload "); |
||
82 | } |
||
83 | |||
84 | // determine correct key for message type (global or via ConfigurationSet) |
||
85 | $type = (array_key_exists('Type', $payload) ? $payload['Type'] : $payload['eventType']); |
||
86 | |||
87 | return $this->processJsonPayload($payload, $type); |
||
0 ignored issues
–
show
|
|||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Process json request from Amazon SES. |
||
92 | * |
||
93 | * http://docs.aws.amazon.com/ses/latest/DeveloperGuide/best-practices-bounces-complaints.html |
||
94 | * |
||
95 | * @param array $payload from Amazon SES |
||
96 | */ |
||
97 | public function processJsonPayload(array $payload, $type) |
||
98 | { |
||
99 | switch ($type) { |
||
100 | case 'SubscriptionConfirmation': |
||
101 | |||
102 | // Confirm Amazon SNS subscription by calling back the SubscribeURL from the playload |
||
103 | try { |
||
104 | $response = $this->httpClient->get($payload['SubscribeURL']); |
||
105 | if (200 == $response->code) { |
||
106 | $this->logger->info('Callback to SubscribeURL from Amazon SNS successfully'); |
||
107 | break; |
||
108 | } |
||
109 | |||
110 | $reason = 'HTTP Code '.$response->code.', '.$response->body; |
||
111 | } catch (UnexpectedResponseException $e) { |
||
112 | $reason = $e->getMessage(); |
||
113 | } |
||
114 | |||
115 | $this->logger->error('Callback to SubscribeURL from Amazon SNS failed, reason: '.$reason); |
||
116 | break; |
||
117 | case 'Notification': |
||
118 | $message = json_decode($payload['Message'], true); |
||
119 | |||
120 | $this->processJsonPayload($message, $message['notificationType']); |
||
121 | break; |
||
122 | case 'Complaint': |
||
123 | foreach ($payload['complaint']['complainedRecipients'] as $complainedRecipient) { |
||
124 | $reason = null; |
||
125 | if (isset($payload['complaint']['complaintFeedbackType'])) { |
||
126 | // http://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#complaint-object |
||
127 | switch ($payload['complaint']['complaintFeedbackType']) { |
||
128 | case 'abuse': |
||
129 | $reason = $this->translator->trans('mautic.email.complaint.reason.abuse'); |
||
130 | break; |
||
131 | case 'fraud': |
||
132 | $reason = $this->translator->trans('mautic.email.complaint.reason.fraud'); |
||
133 | break; |
||
134 | case 'virus': |
||
135 | $reason = $this->translator->trans('mautic.email.complaint.reason.virus'); |
||
136 | break; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | if (null == $reason) { |
||
141 | $reason = $this->translator->trans('mautic.email.complaint.reason.unknown'); |
||
142 | } |
||
143 | |||
144 | $this->transportCallback->addFailureByAddress($complainedRecipient['emailAddress'], $reason, DoNotContact::UNSUBSCRIBED); |
||
145 | |||
146 | $this->logger->debug("Unsubscribe email '".$complainedRecipient['emailAddress']."'"); |
||
147 | } |
||
148 | |||
149 | break; |
||
150 | case 'Bounce': |
||
151 | |||
152 | if ('Permanent' == $payload['bounce']['bounceType']) { |
||
153 | $emailId = null; |
||
154 | |||
155 | if (isset($payload['mail']['headers'])) { |
||
156 | foreach ($payload['mail']['headers'] as $header) { |
||
157 | if ('X-EMAIL-ID' === $header['name']) { |
||
158 | $emailId = $header['value']; |
||
159 | } |
||
160 | } |
||
161 | } |
||
162 | |||
163 | // Get bounced recipients in an array |
||
164 | $bouncedRecipients = $payload['bounce']['bouncedRecipients']; |
||
165 | foreach ($bouncedRecipients as $bouncedRecipient) { |
||
166 | $bounceCode = array_key_exists('diagnosticCode', $bouncedRecipient) ? $bouncedRecipient['diagnosticCode'] : 'unknown'; |
||
167 | $this->transportCallback->addFailureByAddress($bouncedRecipient['emailAddress'], $bounceCode, DoNotContact::BOUNCED, $emailId); |
||
168 | $this->logger->debug("Mark email '".$bouncedRecipient['emailAddress']."' as bounced, reason: ".$bounceCode); |
||
169 | } |
||
170 | } |
||
171 | break; |
||
172 | default: |
||
173 | $this->logger->warn("Received SES webhook of type '$payload[Type]' but couldn't understand payload"); |
||
174 | $this->logger->debug('SES webhook payload: '.json_encode($payload)); |
||
175 | break; |
||
176 | } |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * @throws BounceNotFound |
||
181 | */ |
||
182 | public function processBounce(Message $message) |
||
183 | { |
||
184 | if (self::SNS_ADDRESS !== $message->fromAddress) { |
||
185 | throw new BounceNotFound(); |
||
186 | } |
||
187 | |||
188 | $message = $this->getSnsPayload($message->textPlain); |
||
189 | $typeKey = (array_key_exists('eventType', $message) ? 'eventType' : 'notificationType'); |
||
190 | if ('Bounce' !== $message[$typeKey]) { |
||
191 | throw new BounceNotFound(); |
||
192 | } |
||
193 | |||
194 | $bounce = new BouncedEmail(); |
||
195 | $bounce->setContactEmail($message['bounce']['bouncedRecipients'][0]['emailAddress']) |
||
196 | ->setBounceAddress($message['mail']['source']) |
||
197 | ->setType(Type::UNKNOWN) |
||
198 | ->setRuleCategory(Category::UNKNOWN) |
||
199 | ->setRuleNumber('0013') |
||
200 | ->setIsFinal(true); |
||
201 | |||
202 | return $bounce; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * @return UnsubscribedEmail |
||
207 | * |
||
208 | * @throws UnsubscriptionNotFound |
||
209 | */ |
||
210 | public function processUnsubscription(Message $message) |
||
211 | { |
||
212 | if (self::SNS_ADDRESS !== $message->fromAddress) { |
||
213 | throw new UnsubscriptionNotFound(); |
||
214 | } |
||
215 | |||
216 | $message = $this->getSnsPayload($message->textPlain); |
||
217 | $typeKey = (array_key_exists('eventType', $message) ? 'eventType' : 'notificationType'); |
||
218 | if ('Complaint' !== $message[$typeKey]) { |
||
219 | throw new UnsubscriptionNotFound(); |
||
220 | } |
||
221 | |||
222 | return new UnsubscribedEmail($message['complaint']['complainedRecipients'][0]['emailAddress'], $message['mail']['source']); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * @param string $body |
||
227 | * |
||
228 | * @return array |
||
229 | */ |
||
230 | public function getSnsPayload($body) |
||
231 | { |
||
232 | return json_decode(strtok($body, "\n"), true); |
||
233 | } |
||
234 | } |
||
235 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.