AmazonSESMapper.php
3 years ago
BlacklistErrorMapperTrait.php
3 years ago
ConnectionErrorMapperTrait.php
3 years ago
MailPoetMapper.php
1 year ago
PHPMailMapper.php
4 years ago
PHPMailerMapper.php
3 years ago
SMTPMapper.php
4 years ago
SendGridMapper.php
3 years ago
index.php
3 years ago
MailPoetMapper.php
281 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\Mailer\Methods\ErrorMappers; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use InvalidArgumentException; |
| 9 | use MailPoet\Config\ServicesChecker; |
| 10 | use MailPoet\Mailer\Mailer; |
| 11 | use MailPoet\Mailer\MailerError; |
| 12 | use MailPoet\Mailer\SubscriberError; |
| 13 | use MailPoet\Services\Bridge\API; |
| 14 | use MailPoet\Util\Helpers; |
| 15 | use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; |
| 16 | use MailPoet\Util\Notices\PendingApprovalNotice; |
| 17 | use MailPoet\Util\Notices\UnauthorizedEmailNotice; |
| 18 | use MailPoet\WP\Functions as WPFunctions; |
| 19 | use MailPoetVendor\Carbon\Carbon; |
| 20 | |
| 21 | class MailPoetMapper { |
| 22 | use BlacklistErrorMapperTrait; |
| 23 | use ConnectionErrorMapperTrait; |
| 24 | |
| 25 | const METHOD = Mailer::METHOD_MAILPOET; |
| 26 | |
| 27 | const TEMPORARY_UNAVAILABLE_RETRY_INTERVAL = 300; // seconds |
| 28 | |
| 29 | /** @var ServicesChecker */ |
| 30 | private $servicesChecker; |
| 31 | |
| 32 | /** @var SubscribersFeature */ |
| 33 | private $subscribersFeature; |
| 34 | |
| 35 | /** @var WPFunctions */ |
| 36 | private $wp; |
| 37 | |
| 38 | /** @var PendingApprovalNotice */ |
| 39 | private $pendingApprovalNotice; |
| 40 | |
| 41 | public function __construct( |
| 42 | ServicesChecker $servicesChecker, |
| 43 | SubscribersFeature $subscribers, |
| 44 | WPFunctions $wp, |
| 45 | PendingApprovalNotice $pendingApprovalNotice |
| 46 | ) { |
| 47 | $this->servicesChecker = $servicesChecker; |
| 48 | $this->subscribersFeature = $subscribers; |
| 49 | $this->wp = $wp; |
| 50 | $this->pendingApprovalNotice = $pendingApprovalNotice; |
| 51 | } |
| 52 | |
| 53 | public function getInvalidApiKeyError() { |
| 54 | return new MailerError( |
| 55 | MailerError::OPERATION_SEND, |
| 56 | MailerError::LEVEL_HARD, |
| 57 | __('MailPoet API key is invalid!', 'mailpoet') |
| 58 | ); |
| 59 | } |
| 60 | |
| 61 | public function getErrorForResult(array $result, $subscribers, $sender = null, $newsletter = null) { |
| 62 | $level = MailerError::LEVEL_HARD; |
| 63 | $operation = MailerError::OPERATION_SEND; |
| 64 | $retryInterval = null; |
| 65 | $subscribersErrors = []; |
| 66 | $resultCode = !empty($result['code']) ? $result['code'] : null; |
| 67 | |
| 68 | switch ($resultCode) { |
| 69 | case API::RESPONSE_CODE_NOT_ARRAY: |
| 70 | $message = __('JSON input is not an array', 'mailpoet'); |
| 71 | break; |
| 72 | case API::RESPONSE_CODE_PAYLOAD_ERROR: |
| 73 | $resultParsed = json_decode($result['message'], true); |
| 74 | $message = __('Error while sending.', 'mailpoet'); |
| 75 | |
| 76 | if (is_array($resultParsed)) { |
| 77 | try { |
| 78 | $subscribersErrors = $this->getSubscribersErrors($resultParsed, $subscribers); |
| 79 | $level = MailerError::LEVEL_SOFT; |
| 80 | } catch (InvalidArgumentException $e) { |
| 81 | $message .= ' ' . $e->getMessage(); |
| 82 | } |
| 83 | break; |
| 84 | } |
| 85 | |
| 86 | $appendedMessage = ' ' . $result['message']; |
| 87 | if (isset($result['error']) && in_array($result['error'], [API::ERROR_MESSAGE_DMRAC, API::ERROR_MESSAGE_BULK_EMAIL_FORBIDDEN])) { |
| 88 | $appendedMessage = $this->getDmarcMessage($result, $sender); |
| 89 | |
| 90 | if ($result['error'] === API::ERROR_MESSAGE_BULK_EMAIL_FORBIDDEN) { |
| 91 | $operation = MailerError::OPERATION_DOMAIN_AUTHORIZATION; |
| 92 | $level = MailerError::LEVEL_SOFT; |
| 93 | } |
| 94 | } |
| 95 | $message .= $appendedMessage; |
| 96 | break; |
| 97 | case API::RESPONSE_CODE_INTERNAL_SERVER_ERROR: |
| 98 | case API::RESPONSE_CODE_BAD_GATEWAY: |
| 99 | case API::RESPONSE_CODE_TEMPORARY_UNAVAILABLE: |
| 100 | case API::RESPONSE_CODE_GATEWAY_TIMEOUT: |
| 101 | $message = __('Email service is temporarily not available, please try again in a few minutes.', 'mailpoet'); |
| 102 | $retryInterval = self::TEMPORARY_UNAVAILABLE_RETRY_INTERVAL; |
| 103 | break; |
| 104 | case API::RESPONSE_CODE_CAN_NOT_SEND: |
| 105 | [$operation, $message] = $this->getCanNotSendError($result, $sender); |
| 106 | break; |
| 107 | case API::RESPONSE_CODE_KEY_INVALID: |
| 108 | case API::RESPONSE_CODE_PAYLOAD_TOO_BIG: |
| 109 | default: |
| 110 | $message = $result['message']; |
| 111 | } |
| 112 | return new MailerError($operation, $level, $message, $retryInterval, $subscribersErrors); |
| 113 | } |
| 114 | |
| 115 | private function getSubscribersErrors($resultParsed, $subscribers) { |
| 116 | $errors = []; |
| 117 | foreach ($resultParsed as $resultError) { |
| 118 | if (!is_array($resultError) || !isset($resultError['index']) || !isset($subscribers[$resultError['index']])) { |
| 119 | throw new InvalidArgumentException(__('Invalid MSS response format.', 'mailpoet')); |
| 120 | } |
| 121 | $subscriberErrors = []; |
| 122 | if (isset($resultError['errors']) && is_array($resultError['errors'])) { |
| 123 | array_walk_recursive($resultError['errors'], function($item) use (&$subscriberErrors) { |
| 124 | $subscriberErrors[] = $item; |
| 125 | }); |
| 126 | } |
| 127 | $message = join(', ', $subscriberErrors); |
| 128 | $errors[] = new SubscriberError($subscribers[$resultError['index']], $message); |
| 129 | } |
| 130 | return $errors; |
| 131 | } |
| 132 | |
| 133 | private function getUnauthorizedEmailMessage($sender) { |
| 134 | $email = $sender ? $sender['from_email'] : __('Unknown address', 'mailpoet'); |
| 135 | $validationError = ['invalid_sender_address' => $email]; |
| 136 | $notice = new UnauthorizedEmailNotice($this->wp, null); |
| 137 | $message = $notice->getMessage($validationError); |
| 138 | return $message; |
| 139 | } |
| 140 | |
| 141 | private function getSubscribersLimitReachedMessage(): string { |
| 142 | $message = __('You have reached the subscriber limit of your plan. Please [link1]upgrade your plan[/link1], or [link2]contact our support team[/link2] if you have any questions.', 'mailpoet'); |
| 143 | $message = Helpers::replaceLinkTags( |
| 144 | $message, |
| 145 | 'https://account.mailpoet.com/account/', |
| 146 | [ |
| 147 | 'target' => '_blank', |
| 148 | 'rel' => 'noopener noreferrer', |
| 149 | ], |
| 150 | 'link1' |
| 151 | ); |
| 152 | $message = Helpers::replaceLinkTags( |
| 153 | $message, |
| 154 | 'https://www.mailpoet.com/support/', |
| 155 | [ |
| 156 | 'target' => '_blank', |
| 157 | 'rel' => 'noopener noreferrer', |
| 158 | ], |
| 159 | 'link2' |
| 160 | ); |
| 161 | |
| 162 | return "{$message}<br/>"; |
| 163 | } |
| 164 | |
| 165 | private function getAccountBannedMessage(): string { |
| 166 | $message = __('The MailPoet Sending Service has been temporarily suspended for your site due to a high number of [link1]undeliverable emails or emails marked as unwanted by recipients[/link1]. Please [link2]contact our support team[/link2] to resolve the issue.', 'mailpoet'); |
| 167 | $message = Helpers::replaceLinkTags( |
| 168 | $message, |
| 169 | 'https://kb.mailpoet.com/article/231-sending-does-not-work#suspended', |
| 170 | [ |
| 171 | 'target' => '_blank', |
| 172 | 'rel' => 'noopener noreferrer', |
| 173 | ], |
| 174 | 'link1' |
| 175 | ); |
| 176 | $message = Helpers::replaceLinkTags( |
| 177 | $message, |
| 178 | 'https://www.mailpoet.com/support-for-banned-users/', |
| 179 | [ |
| 180 | 'target' => '_blank', |
| 181 | 'rel' => 'noopener noreferrer', |
| 182 | ], |
| 183 | 'link2' |
| 184 | ); |
| 185 | |
| 186 | return "{$message}<br/>"; |
| 187 | } |
| 188 | |
| 189 | private function getDmarcMessage($result, $sender): string { |
| 190 | $messageToAppend = __('[link1]Click here to start the authentication[/link1].', 'mailpoet'); |
| 191 | $senderEmail = $sender['from_email'] ?? ''; |
| 192 | |
| 193 | $appendMessage = Helpers::replaceLinkTags( |
| 194 | $messageToAppend, |
| 195 | '#', |
| 196 | [ |
| 197 | 'class' => 'mailpoet-js-button-authorize-email-and-sender-domain', |
| 198 | 'data-email' => $senderEmail, |
| 199 | 'data-type' => 'domain', |
| 200 | 'rel' => 'noopener noreferrer', |
| 201 | ], |
| 202 | 'link1' |
| 203 | ); |
| 204 | $final = ' ' . $result['message'] . ' ' . $appendMessage; |
| 205 | return $final; |
| 206 | } |
| 207 | |
| 208 | private function getEmailVolumeLimitReachedMessage(): string { |
| 209 | $partialApiKey = $this->servicesChecker->generatePartialApiKey(); |
| 210 | $emailVolumeLimit = $this->subscribersFeature->getEmailVolumeLimit(); |
| 211 | $date = Carbon::now()->startOfMonth()->addMonth(); |
| 212 | if ($emailVolumeLimit) { |
| 213 | $message = sprintf( |
| 214 | // translators: %1$s is email volume limit and %2$s the date when you can resume sending. |
| 215 | __('You have sent more emails this month than your MailPoet plan includes (%1$s), and sending has been temporarily paused. To continue sending with MailPoet Sending Service please [link]upgrade your plan[/link], or wait until sending is automatically resumed on %2$s.', 'mailpoet'), |
| 216 | $emailVolumeLimit, |
| 217 | $this->wp->dateI18n($this->wp->getOption('date_format'), $date->getTimestamp()) |
| 218 | ); |
| 219 | } else { |
| 220 | $message = sprintf( |
| 221 | // translators: %1$s the date when you can resume sending. |
| 222 | __('You have sent more emails this month than your MailPoet plan includes, and sending has been temporarily paused. To continue sending with MailPoet Sending Service please [link]upgrade your plan[/link], or wait until sending is automatically resumed on %1$s.', 'mailpoet'), |
| 223 | $this->wp->dateI18n($this->wp->getOption('date_format'), $date->getTimestamp()) |
| 224 | ); |
| 225 | } |
| 226 | |
| 227 | $message = Helpers::replaceLinkTags( |
| 228 | $message, |
| 229 | "https://account.mailpoet.com/orders/upgrade/{$partialApiKey}", |
| 230 | [ |
| 231 | 'target' => '_blank', |
| 232 | 'rel' => 'noopener noreferrer', |
| 233 | ] |
| 234 | ); |
| 235 | |
| 236 | return "{$message}<br/>"; |
| 237 | } |
| 238 | |
| 239 | /** |
| 240 | * Returns error $message and $operation for API::RESPONSE_CODE_CAN_NOT_SEND |
| 241 | */ |
| 242 | private function getCanNotSendError(array $result, $sender): array { |
| 243 | if ($result['error'] === API::ERROR_MESSAGE_PENDING_APPROVAL) { |
| 244 | $operation = MailerError::OPERATION_PENDING_APPROVAL; |
| 245 | $message = $this->pendingApprovalNotice->getPendingApprovalMessage() . '<br/>'; |
| 246 | return [$operation, $message]; |
| 247 | } |
| 248 | |
| 249 | // Backward compatibility for older blocked keys. |
| 250 | // Exceeded subscribers limit used to use the same error message as insufficient privileges. |
| 251 | // We can change the message to "Insufficient privileges" like wording a couple of months after releasing SHOP-1228 |
| 252 | if ($result['error'] === API::ERROR_MESSAGE_INSUFFICIENT_PRIVILEGES) { |
| 253 | $operation = MailerError::OPERATION_INSUFFICIENT_PRIVILEGES; |
| 254 | $message = $this->getSubscribersLimitReachedMessage(); |
| 255 | return [$operation, $message]; |
| 256 | } |
| 257 | |
| 258 | if ($result['error'] === API::ERROR_MESSAGE_SUBSCRIBERS_LIMIT_REACHED) { |
| 259 | $operation = MailerError::OPERATION_SUBSCRIBER_LIMIT_REACHED; |
| 260 | $message = $this->getSubscribersLimitReachedMessage(); |
| 261 | return [$operation, $message]; |
| 262 | } |
| 263 | |
| 264 | if ($result['error'] === API::ERROR_MESSAGE_EMAIL_VOLUME_LIMIT_REACHED) { |
| 265 | $operation = MailerError::OPERATION_EMAIL_LIMIT_REACHED; |
| 266 | $message = $this->getEmailVolumeLimitReachedMessage(); |
| 267 | return [$operation, $message]; |
| 268 | } |
| 269 | |
| 270 | if ($result['error'] === API::ERROR_MESSAGE_INVALID_FROM) { |
| 271 | $operation = MailerError::OPERATION_AUTHORIZATION; |
| 272 | $message = $this->getUnauthorizedEmailMessage($sender); |
| 273 | return [$operation, $message]; |
| 274 | } |
| 275 | |
| 276 | $message = $this->getAccountBannedMessage(); |
| 277 | $operation = MailerError::OPERATION_SEND; |
| 278 | return [$operation, $message]; |
| 279 | } |
| 280 | } |
| 281 |