Refactor sending methods to use error mappers

We want to add some logic to error handling.
This commit extracts error handling code from sending methods classes,
which already do a lot of other stuff, to error mappers which are responsible
for creating proper error object (MailerError). This error object is a replacement
for assoc. array which already had some special keys for certain usecases and
can not be properly type hinted.

[MAILPOET-1154]
This commit is contained in:
Rostislav Wolny
2018-08-30 10:42:58 +02:00
parent 8cf5d17cfd
commit 0923c892c1
22 changed files with 517 additions and 211 deletions

View File

@ -34,7 +34,7 @@ class Mailer extends APIEndpoint {
if($result['response'] === false) {
$error = sprintf(
__('The email could not be sent: %s', 'mailpoet'),
$result['error_message']
$result['error']->getMessage()
);
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
} else {

View File

@ -5,6 +5,7 @@ use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\MailerLog;
use MailPoet\Models\ScheduledTask as ScheduledTaskModel;
use MailPoet\Models\StatisticsNewsletters as StatisticsNewslettersModel;
@ -164,10 +165,12 @@ class SendingQueue {
);
// log error message and schedule retry/pause sending
if($send_result['response'] === false) {
if(isset($send_result['retry_interval'])) {
MailerLog::processNonBlockingError($send_result['operation'], $send_result['error_message'], $send_result['retry_interval']);
$error = $send_result['error'];
assert($error instanceof MailerError);
if($error->getRetryInterval() !== null) {
MailerLog::processNonBlockingError($error->getOperation(), $error->getMessage(), $error->getRetryInterval());
} else {
MailerLog::processError($send_result['operation'], $send_result['error_message']);
MailerLog::processError($error->getOperation(), $error->getMessage());
}
}
// update processed/to process list

View File

@ -1,6 +1,11 @@
<?php
namespace MailPoet\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\AmazonSESMapper;
use MailPoet\Mailer\Methods\ErrorMappers\MailPoetMapper;
use MailPoet\Mailer\Methods\ErrorMappers\PHPMailMapper;
use MailPoet\Mailer\Methods\ErrorMappers\SendGridMapper;
use MailPoet\Mailer\Methods\ErrorMappers\SMTPMapper;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
@ -42,28 +47,32 @@ class Mailer {
$this->mailer_config['secret_key'],
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new AmazonSESMapper()
);
break;
case self::METHOD_MAILPOET:
$mailer_instance = new $this->mailer_config['class'](
$this->mailer_config['mailpoet_api_key'],
$this->sender,
$this->reply_to
$this->reply_to,
new MailPoetMapper()
);
break;
case self::METHOD_SENDGRID:
$mailer_instance = new $this->mailer_config['class'](
$this->mailer_config['api_key'],
$this->sender,
$this->reply_to
$this->reply_to,
new SendGridMapper()
);
break;
case self::METHOD_PHPMAIL:
$mailer_instance = new $this->mailer_config['class'](
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new PHPMailMapper()
);
break;
case self::METHOD_SMTP:
@ -76,7 +85,8 @@ class Mailer {
$this->mailer_config['encryption'],
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new SMTPMapper()
);
break;
default:
@ -166,25 +176,16 @@ class Mailer {
return sprintf('=?utf-8?B?%s?=', base64_encode($name));
}
static function formatMailerConnectionErrorResult($error_message) {
return array(
static function formatMailerErrorResult(MailerError $error) {
return [
'response' => false,
'operation' => 'connect',
'error_message' => $error_message
);
}
static function formatMailerSendErrorResult($error_message) {
return array(
'response' => false,
'operation' => 'send',
'error_message' => $error_message
);
'error' => $error,
];
}
static function formatMailerSendSuccessResult() {
return array(
return [
'response' => true
);
];
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace MailPoet\Mailer;
class MailerError {
const OPERATION_CONNECT = 'connect';
const OPERATION_SEND = 'send';
const LEVEL_HARD = 'hard';
const LEVEL_SOFT = 'soft';
/** @var string */
private $operation;
/** @var string */
private $level;
/** @var string|null */
private $message;
/** @var int|null */
private $retry_interval;
/**
* @param string $operation
* @param string $level
* @param null|string $message
* @param int|null $retry_interval
*/
function __construct($operation, $level, $message = null, $retry_interval = null) {
$this->operation = $operation;
$this->level = $level;
$this->message = $message;
$this->retry_interval = $retry_interval;
}
/**
* @return string
*/
function getOperation() {
return $this->operation;
}
/**
* @return string
*/
function getLevel() {
return $this->level;
}
/**
* @return null|string
*/
function getMessage() {
return $this->message;
}
/**
* @return int|null
*/
function getRetryInterval() {
return $this->retry_interval;
}
}

View File

@ -2,6 +2,7 @@
namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\AmazonSESMapper;
use MailPoet\WP\Functions as WPFunctions;
if(!defined('ABSPATH')) exit;
@ -28,7 +29,18 @@ class AmazonSES {
'EU (Ireland)' => 'eu-west-1'
);
function __construct($region, $access_key, $secret_key, $sender, $reply_to, $return_path) {
/** @var AmazonSESMapper */
private $error_mapper;
function __construct(
$region,
$access_key,
$secret_key,
$sender,
$reply_to,
$return_path,
AmazonSESMapper $error_mapper
) {
$this->aws_access_key = $access_key;
$this->aws_secret_key = $secret_key;
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
@ -48,6 +60,7 @@ class AmazonSES {
$this->sender['from_email'];
$this->date = gmdate('Ymd\THis\Z');
$this->date_without_time = gmdate('Ymd');
$this->error_mapper = $error_mapper;
}
function send($newsletter, $subscriber, $extra_params = array()) {
@ -57,20 +70,17 @@ class AmazonSES {
$this->request($newsletter, $subscriber, $extra_params)
);
} catch(\Exception $e) {
return Mailer::formatMailerSendErrorResult($e->getMessage());
$error = $this->error_mapper->getErrorFromException($e);
return Mailer::formatMailerErrorResult($error);
}
if(is_wp_error($result)) {
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
$error = $this->error_mapper->getConnectionError($result->get_error_message());
return Mailer::formatMailerErrorResult($error);
}
if(WPFunctions::wpRemoteRetrieveResponseCode($result) !== 200) {
$response = simplexml_load_string(WPFunctions::wpRemoteRetrieveBody($result));
$response = ($response) ?
$response->Error->Message->__toString() :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_AMAZONSES);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($response);
$error = $this->error_mapper->getErrorFromResponse($response, $subscriber, $extra_params);
return Mailer::formatMailerErrorResult($error);
}
return Mailer::formatMailerSendSuccessResult();
}

View File

@ -0,0 +1,23 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Mailer;
class AmazonSESMapper {
use ConnectionErrorMapperTrait;
function getErrorFromException(\Exception $e) {
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $e->getMessage());
}
function getErrorFromResponse($response, $subscriber, $extra_params) {
$response = ($response) ?
$response->Error->Message->__toString() :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_AMAZONSES);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $response);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
trait ConnectionErrorMapperTrait {
function getConnectionError($message) {
return new MailerError(
MailerError::OPERATION_CONNECT,
MailerError::LEVEL_HARD,
$message
);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Services\Bridge\API;
if(!defined('ABSPATH')) exit;
class MailPoetMapper {
use ConnectionErrorMapperTrait;
const TEMPORARY_UNAVAILABLE_RETRY_INTERVAL = 300; // seconds
function getInvalidApiKeyError() {
return new MailerError(
MailerError::OPERATION_SEND,
MailerError::LEVEL_HARD,
__('MailPoet API key is invalid!', 'mailpoet')
);
}
function getErrorForResult(array $result, $subscribers) {
$message = $result['message'];
$level = MailerError::LEVEL_HARD;
$retry_interval = null;
if(!empty($result['code'])) {
switch($result['code']) {
case API::RESPONSE_CODE_NOT_ARRAY:
$message = __('JSON input is not an array', 'mailpoet');
break;
case API::RESPONSE_CODE_PAYLOAD_ERROR:
$message = $this->parseErrorResponse($result['message'], $subscribers);
break;
case API::RESPONSE_CODE_TEMPORARY_UNAVAILABLE:
$message = __('Email service is temporarily not available, please try again in a few minutes.', 'mailpoet');
$retry_interval = self::TEMPORARY_UNAVAILABLE_RETRY_INTERVAL;
break;
case API::RESPONSE_CODE_KEY_INVALID:
case API::RESPONSE_CODE_PAYLOAD_TOO_BIG:
default:
$message = $result['message'];
}
}
return new MailerError(MailerError::OPERATION_SEND, $level, $message, $retry_interval);
}
private function parseErrorResponse($result, $subscriber) {
$result_parsed = json_decode($result, true);
$errors = [];
if(is_array($result_parsed)) {
foreach($result_parsed as $result_error) {
$errors[] = $this->processSingleSubscriberError($result_error, $subscriber);
}
}
if(!empty($errors)) {
return __('Error while sending: ', 'mailpoet') . join(', ', $errors);
} else {
return __('Error while sending newsletters. ', 'mailpoet') . $result;
}
}
private function processSingleSubscriberError($result_error, $subscriber) {
$error = '';
if(is_array($result_error)) {
$subscriber_errors = [];
if(isset($result_error['errors']) && is_array($result_error['errors'])) {
array_walk_recursive($result_error['errors'], function($item) use (&$subscriber_errors) {
$subscriber_errors[] = $item;
});
}
$error .= join(', ', $subscriber_errors);
if(isset($result_error['index']) && isset($subscriber[$result_error['index']])) {
$error = '(' . $subscriber[$result_error['index']] . ': ' . $error . ')';
}
}
return $error;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Mailer;
class PHPMailMapper {
use ConnectionErrorMapperTrait;
function getErrorFromException(\Exception $e) {
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $e->getMessage());
}
function getErrorForSubscriber($subscriber, $extra_params) {
$message = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_PHPMAIL);
if(empty($extra_params['test_email'])) {
$message .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $message);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Mailer;
class SMTPMapper {
use ConnectionErrorMapperTrait;
function getErrorFromException(\Exception $e) {
// remove redundant information appended by Swift logger to exception messages
$message = explode(PHP_EOL, $e->getMessage());
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $message[0]);
}
function getErrorFromLog($log, $subscriber, $extra_params = []) {
// extract error message from log
preg_match('/!! (.*?)>>/ism', $log, $message);
if(!empty($message[1])) {
$message = $message[1];
// remove line breaks from the message due to how logger's dump() method works
$message = preg_replace('/\r|\n/', '', $message);
} else {
$message = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SMTP);
}
if(empty($extra_params['test_email'])) {
$message .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $message);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace MailPoet\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Mailer;
class SendGridMapper {
use ConnectionErrorMapperTrait;
function getErrorFromResponse($response, $subscriber, $extra_params) {
$response = (!empty($response['errors'][0])) ?
$response['errors'][0] :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SENDGRID);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return new MailerError(MailerError::OPERATION_SEND, MailerError::LEVEL_HARD, $response);
}
}

View File

@ -3,31 +3,32 @@ namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Config\ServicesChecker;
use MailPoet\Mailer\Methods\ErrorMappers\MailPoetMapper;
use MailPoet\Services\Bridge;
use MailPoet\Services\Bridge\API;
if(!defined('ABSPATH')) exit;
class MailPoet {
const TEMPORARY_UNAVAILABLE_RETRY_INTERVAL = 300; // seconds
public $api;
public $sender;
public $reply_to;
public $services_checker;
function __construct($api_key, $sender, $reply_to) {
/** @var MailPoetMapper */
private $error_mapper;
function __construct($api_key, $sender, $reply_to, MailPoetMapper $error_mapper) {
$this->api = new API($api_key);
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->services_checker = new ServicesChecker(false);
$this->services_checker = new ServicesChecker();
$this->error_mapper = $error_mapper;
}
function send($newsletter, $subscriber, $extra_params = array()) {
if($this->services_checker->isMailPoetAPIKeyValid() === false) {
$response = __('MailPoet API key is invalid!', 'mailpoet');
return Mailer::formatMailerSendErrorResult($response);
return Mailer::formatMailerErrorResult($this->error_mapper->getInvalidApiKeyError());
}
$message_body = $this->getBody($newsletter, $subscriber, $extra_params);
@ -35,9 +36,11 @@ class MailPoet {
switch($result['status']) {
case API::SENDING_STATUS_CONNECTION_ERROR:
return Mailer::formatMailerConnectionErrorResult($result['message']);
$error = $this->error_mapper->getConnectionError($result['message']);
return Mailer::formatMailerErrorResult($error);
case API::SENDING_STATUS_SEND_ERROR:
return $this->processSendError($result, $subscriber);
$error = $this->processSendError($result, $subscriber);
return Mailer::formatMailerErrorResult($error);
case API::SENDING_STATUS_OK:
default:
return Mailer::formatMailerSendSuccessResult();
@ -45,27 +48,10 @@ class MailPoet {
}
function processSendError($result, $subscriber) {
if(!empty($result['code'])) {
switch($result['code']) {
case API::RESPONSE_CODE_NOT_ARRAY:
return Mailer::formatMailerSendErrorResult(__('JSON input is not an array', 'mailpoet'));
case API::RESPONSE_CODE_PAYLOAD_TOO_BIG:
return Mailer::formatMailerSendErrorResult($result['message']);
case API::RESPONSE_CODE_PAYLOAD_ERROR:
$error = $this->parseErrorResponse($result['message'], $subscriber);
return Mailer::formatMailerSendErrorResult($error);
case API::RESPONSE_CODE_TEMPORARY_UNAVAILABLE:
$error = Mailer::formatMailerSendErrorResult(__('Email service is temporarily not available, please try again in a few minutes.', 'mailpoet'));
$error['retry_interval'] = self::TEMPORARY_UNAVAILABLE_RETRY_INTERVAL;
return $error;
case API::RESPONSE_CODE_KEY_INVALID:
Bridge::invalidateKey();
break;
default:
return Mailer::formatMailerSendErrorResult($result['message']);
}
if(!empty($result['code']) && $result['code'] === API::RESPONSE_CODE_KEY_INVALID) {
Bridge::invalidateKey();
}
return Mailer::formatMailerSendErrorResult($result['message']);
return $this->error_mapper->getErrorForResult($result, $subscriber);
}
function processSubscriber($subscriber) {
@ -128,37 +114,4 @@ class MailPoet {
}
return $body;
}
private function parseErrorResponse($result, $subscriber) {
$result_parsed = json_decode($result, true);
$errors = [];
if(is_array($result_parsed)) {
foreach($result_parsed as $result_error) {
$errors[] = $this->processSingleSubscriberError($result_error, $subscriber);
}
}
if(!empty($errors)) {
return __('Error while sending: ', 'mailpoet') . join(', ', $errors);
} else {
return __('Error while sending newsletters. ', 'mailpoet') . $result;
}
}
private function processSingleSubscriberError($result_error, $subscriber) {
$error = '';
if(is_array($result_error)) {
$subscriber_errors = [];
if(isset($result_error['errors']) && is_array($result_error['errors'])) {
array_walk_recursive($result_error['errors'], function($item) use (&$subscriber_errors) {
$subscriber_errors[] = $item;
});
}
$error .= join(', ', $subscriber_errors);
if(isset($result_error['index']) && isset($subscriber[$result_error['index']])) {
$error = '(' . $subscriber[$result_error['index']] . ': ' . $error . ')';
}
}
return $error;
}
}

View File

@ -3,6 +3,7 @@
namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\PHPMailMapper;
if(!defined('ABSPATH')) exit;
@ -14,13 +15,17 @@ class PHPMail {
public $return_path;
public $mailer;
function __construct($sender, $reply_to, $return_path) {
/** @var PHPMailMapper */
private $error_mapper;
function __construct($sender, $reply_to, $return_path, PHPMailMapper $error_mapper) {
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->return_path = ($return_path) ?
$return_path :
$this->sender['from_email'];
$this->mailer = $this->buildMailer();
$this->error_mapper = $error_mapper;
}
function send($newsletter, $subscriber, $extra_params = array()) {
@ -28,16 +33,13 @@ class PHPMail {
$mailer = $this->configureMailerWithMessage($newsletter, $subscriber, $extra_params);
$result = $mailer->send();
} catch(\Exception $e) {
return Mailer::formatMailerSendErrorResult($e->getMessage());
return Mailer::formatMailerErrorResult($this->error_mapper->getErrorFromException($e));
}
if($result === true) {
return Mailer::formatMailerSendSuccessResult();
} else {
$result = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_PHPMAIL);
if(empty($extra_params['test_email'])) {
$result .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($result);
$error = $this->error_mapper->getErrorForSubscriber($subscriber, $extra_params);
return Mailer::formatMailerErrorResult($error);
}
}

View File

@ -2,6 +2,7 @@
namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\SMTPMapper;
use MailPoet\WP\Hooks;
if(!defined('ABSPATH')) exit;
@ -19,9 +20,12 @@ class SMTP {
public $mailer;
const SMTP_CONNECTION_TIMEOUT = 15; // seconds
/** @var SMTPMapper */
private $error_mapper;
function __construct(
$host, $port, $authentication, $login = null, $password = null, $encryption,
$sender, $reply_to, $return_path) {
$sender, $reply_to, $return_path, SMTPMapper $error_mapper) {
$this->host = $host;
$this->port = $port;
$this->authentication = $authentication;
@ -36,6 +40,7 @@ class SMTP {
$this->mailer = $this->buildMailer();
$this->mailer_logger = new \Swift_Plugins_Loggers_ArrayLogger();
$this->mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($this->mailer_logger));
$this->error_mapper = $error_mapper;
}
function send($newsletter, $subscriber, $extra_params = array()) {
@ -43,13 +48,16 @@ class SMTP {
$message = $this->createMessage($newsletter, $subscriber, $extra_params);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
return Mailer::formatMailerSendErrorResult(
$this->processExceptionMessage($e->getMessage())
return Mailer::formatMailerErrorResult(
$this->error_mapper->getErrorFromException($e)
);
}
return ($result === 1) ?
Mailer::formatMailerSendSuccessResult() :
Mailer::formatMailerSendErrorResult($this->processLogMessage($subscriber, $extra_params));
if($result === 1) {
return Mailer::formatMailerSendSuccessResult();
} else {
$error = $this->error_mapper->getErrorFromLog($this->mailer_logger->dump(), $subscriber, $extra_params);
return Mailer::formatMailerErrorResult($error);
}
}
function buildMailer() {
@ -107,27 +115,4 @@ class SMTP {
(isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
function processLogMessage($subscriber, $extra_params = array(), $log = false) {
$log = ($log) ? $log : $this->mailer_logger->dump();
// extract error message from log
preg_match('/!! (.*?)>>/ism', $log, $message);
if(!empty($message[1])) {
$message = $message[1];
// remove line breaks from the message due to how logger's dump() method works
$message = preg_replace('/\r|\n/', '', $message);
} else {
$message = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SMTP);
}
if(empty($extra_params['test_email'])) {
$message .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return $message;
}
function processExceptionMessage($message) {
// remove redundant information appended by Swift logger to exception messages
$message = explode(PHP_EOL, $message);
return $message[0];
}
}

View File

@ -3,6 +3,7 @@
namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\SendGridMapper;
use MailPoet\WP\Functions as WPFunctions;
if(!defined('ABSPATH')) exit;
@ -13,10 +14,14 @@ class SendGrid {
public $sender;
public $reply_to;
function __construct($api_key, $sender, $reply_to) {
/** @var SendGridMapper */
private $error_mapper;
function __construct($api_key, $sender, $reply_to, SendGridMapper $error_mapper) {
$this->api_key = $api_key;
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->error_mapper = $error_mapper;
}
function send($newsletter, $subscriber, $extra_params = array()) {
@ -25,17 +30,13 @@ class SendGrid {
$this->request($newsletter, $subscriber, $extra_params)
);
if(is_wp_error($result)) {
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
$error = $this->error_mapper->getConnectionError($result->get_error_message());
return Mailer::formatMailerErrorResult($error);
}
if(WPFunctions::wpRemoteRetrieveResponseCode($result) !== 200) {
$response = json_decode($result['body'], true);
$response = (!empty($response['errors'][0])) ?
$response['errors'][0] :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SENDGRID);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($response);
$error = $this->error_mapper->getErrorFromResponse($response, $subscriber, $extra_params);
return Mailer::formatMailerErrorResult($error);
}
return Mailer::formatMailerSendSuccessResult();
}

View File

@ -1,7 +1,9 @@
<?php
namespace MailPoet\Test\Mailer\Methods;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Methods\AmazonSES;
use MailPoet\Mailer\Methods\ErrorMappers\AmazonSESMapper;
class AmazonSESTest extends \MailPoetTest {
function _before() {
@ -34,7 +36,8 @@ class AmazonSESTest extends \MailPoetTest {
$this->settings['secret_key'],
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new AmazonSESMapper()
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
@ -69,7 +72,8 @@ class AmazonSESTest extends \MailPoetTest {
$this->settings['secret_key'],
$this->sender,
$this->reply_to,
$return_path = false
$return_path = false,
new AmazonSESMapper()
);
expect($mailer->return_path)->equals($this->sender['from_email']);
}
@ -82,7 +86,8 @@ class AmazonSESTest extends \MailPoetTest {
$this->settings['secret_key'],
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new AmazonSESMapper()
);
$this->fail('Unsupported region exception was not thrown');
} catch(\Exception $e) {
@ -223,7 +228,8 @@ class AmazonSESTest extends \MailPoetTest {
$invalid_subscriber
);
expect($result['response'])->false();
expect($result['error_message'])->contains('does not comply with RFC 2822');
expect($result['error'])->isInstanceOf(MailerError::class);
expect($result['error']->getMessage())->contains('does not comply with RFC 2822');
}
function testItCanSend() {

View File

@ -0,0 +1,80 @@
<?php
namespace MailPoet\Test\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Methods\ErrorMappers\MailPoetMapper;
use MailPoet\Services\Bridge\API;
class MailPoetMapperTest extends \MailPoetTest {
/** @var MailPoetMapper */
private $mapper;
/** @var array */
private $subscribers;
function _before() {
$this->mapper = new MailPoetMapper();
$this->subscribers = ['a@example.com', 'c d <b@example.com>'];
}
function testCreateConnectionError() {
$error = $this->mapper->getConnectionError('connection error');
expect($error)->isInstanceOf(MailerError::class);
expect($error->getOperation())->equals(MailerError::OPERATION_CONNECT);
expect($error->getLevel())->equals(MailerError::LEVEL_HARD);
expect($error->getMessage())->equals('connection error');
}
function testGetErrorNotArray() {
$api_result = [
'code' => API::RESPONSE_CODE_NOT_ARRAY,
'status' => API::SENDING_STATUS_SEND_ERROR,
'message' => 'error not array',
];
$error = $this->mapper->getErrorForResult($api_result, $this->subscribers);
expect($error)->isInstanceOf(MailerError::class);
expect($error->getOperation())->equals(MailerError::OPERATION_SEND);
expect($error->getLevel())->equals(MailerError::LEVEL_HARD);
expect($error->getMessage())->equals('JSON input is not an array');
}
function testGetErrorPayloadTooBig() {
$api_result = [
'code' => API::RESPONSE_CODE_PAYLOAD_TOO_BIG,
'status' => API::SENDING_STATUS_SEND_ERROR,
'message' => 'error too big',
];
$error = $this->mapper->getErrorForResult($api_result, $this->subscribers);
expect($error)->isInstanceOf(MailerError::class);
expect($error->getOperation())->equals(MailerError::OPERATION_SEND);
expect($error->getLevel())->equals(MailerError::LEVEL_HARD);
expect($error->getMessage())->equals('error too big');
}
function testGetPayloadError() {
$api_result = [
'code' => API::RESPONSE_CODE_PAYLOAD_ERROR,
'status' => API::SENDING_STATUS_SEND_ERROR,
'message' => 'Api Error',
];
$error = $this->mapper->getErrorForResult($api_result, $this->subscribers);
expect($error)->isInstanceOf(MailerError::class);
expect($error->getOperation())->equals(MailerError::OPERATION_SEND);
expect($error->getLevel())->equals(MailerError::LEVEL_HARD);
expect($error->getMessage())->equals('Error while sending newsletters. Api Error');
}
function testGetPayloadErrorWithErrorMessage() {
$api_result = [
'code' => API::RESPONSE_CODE_PAYLOAD_ERROR,
'status' => API::SENDING_STATUS_SEND_ERROR,
'message' => '[{"index":0,"errors":{"subject":"subject is missing"}},{"index":1,"errors":{"subject":"subject is missing"}}]'
];
$error = $this->mapper->getErrorForResult($api_result, $this->subscribers);
expect($error)->isInstanceOf(MailerError::class);
expect($error->getOperation())->equals(MailerError::OPERATION_SEND);
expect($error->getLevel())->equals(MailerError::LEVEL_HARD);
expect($error->getMessage())->equals('Error while sending: (a@example.com: subject is missing), (c d <b@example.com>: subject is missing)');
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace MailPoet\Test\Mailer\Methods\ErrorMappers;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\SMTPMapper;
class SMTPMapperTest extends \MailPoetTest {
/** @var SMTPMapper */
private $mapper;
function _before() {
$this->mapper = new SMTPMapper();
}
function testItCanProcessExceptionMessage() {
$message = 'Connection could not be established with host localhost [Connection refused #111]' . PHP_EOL
. 'Log data:' . PHP_EOL
. '++ Starting Swift_SmtpTransport' . PHP_EOL
. '!! Connection could not be established with host localhost [Connection refused #111] (code: 0)';
$error = $this->mapper->getErrorFromException(new \Exception($message));
expect($error->getMessage())
->equals('Connection could not be established with host localhost [Connection refused #111]');
}
function testItCanProcessLogMessageWhenOneExists() {
$log = '++ Swift_SmtpTransport started' . PHP_EOL
. '>> MAIL FROM:<moi@mrcasual.com>' . PHP_EOL
. '<< 250 OK' . PHP_EOL
. '>> RCPT TO:<test2@ietsdoenofferte.nl>' . PHP_EOL
. '<< 550 No such recipient here' . PHP_EOL
. '!! Expected response code 250/251/252 but got code "550", with message "550 No such recipient here' . PHP_EOL
. '" (code: 550)' . PHP_EOL
. '>> RSET' . PHP_EOL
. '<< 250 Reset OK' . PHP_EOL;
$error = $this->mapper->getErrorFromLog($log, 'test@example.com', []);
expect($error->getMessage())
->equals('Expected response code 250/251/252 but got code "550", with message "550 No such recipient here" (code: 550) Unprocessed subscriber: test@example.com');
}
function testItReturnsGenericMessageWhenLogMessageDoesNotExist() {
$error = $this->mapper->getErrorFromLog(null, 'test@example.com');
expect($error->getMessage())
->equals(Mailer::METHOD_SMTP . ' has returned an unknown error. Unprocessed subscriber: test@example.com');
}
}

View File

@ -3,6 +3,8 @@ namespace MailPoet\Test\Mailer\Methods;
use Codeception\Util\Stub;
use MailPoet\Config\ServicesChecker;
use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\Methods\ErrorMappers\MailPoetMapper;
use MailPoet\Mailer\Methods\MailPoet;
use MailPoet\Services\Bridge\API;
@ -27,7 +29,8 @@ class MailPoetAPITest extends \MailPoetTest {
$this->mailer = new MailPoet(
$this->settings['api_key'],
$this->sender,
$this->reply_to
$this->reply_to,
new MailPoetMapper()
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
@ -161,11 +164,9 @@ class MailPoetAPITest extends \MailPoetTest {
$this
);
$result = $this->mailer->send($this->newsletter, $this->subscriber);
expect($result)->equals([
'response' => false,
'operation' => 'connect',
'error_message' => 'connection error',
]);
expect($result['response'])->false();
expect($result['error'])->isInstanceOf(MailerError::class);
expect($result['error']->getOperation())->equals(MailerError::OPERATION_CONNECT);
}
function testFormatErrorNotArray() {
@ -179,11 +180,9 @@ class MailPoetAPITest extends \MailPoetTest {
$this
);
$result = $this->mailer->send($this->newsletter, $this->subscriber);
expect($result)->equals([
'response' => false,
'operation' => 'send',
'error_message' => 'JSON input is not an array',
]);
expect($result['response'])->false();
expect($result['error'])->isInstanceOf(MailerError::class);
expect($result['error']->getOperation())->equals(MailerError::OPERATION_SEND);
}
function testFormatErrorTooBig() {
@ -197,11 +196,8 @@ class MailPoetAPITest extends \MailPoetTest {
$this
);
$result = $this->mailer->send($this->newsletter, $this->subscriber);
expect($result)->equals([
'response' => false,
'operation' => 'send',
'error_message' => 'error too big',
]);
expect($result['response'])->false();
expect($result['error'])->isInstanceOf(MailerError::class);
}
function testFormatPayloadError() {
@ -215,11 +211,9 @@ class MailPoetAPITest extends \MailPoetTest {
$this
);
$result = $this->mailer->send([$this->newsletter, $this->newsletter], ['a@example.com', 'c d <b@example.com>']);
expect($result)->equals([
'response' => false,
'operation' => 'send',
'error_message' => 'Error while sending newsletters. Api Error',
]);
expect($result['response'])->false();
expect($result['error'])->isInstanceOf(MailerError::class);
expect($result['error']->getOperation())->equals(MailerError::OPERATION_SEND);
}
function testFormatPayloadErrorWithErrorMessage() {
@ -233,12 +227,8 @@ class MailPoetAPITest extends \MailPoetTest {
$this
);
$result = $this->mailer->send([$this->newsletter, $this->newsletter], ['a@example.com', 'c d <b@example.com>']);
expect($result)->equals([
'response' => false,
'operation' => 'send',
'error_message' => 'Error while sending: (a@example.com: subject is missing), (c d <b@example.com>: subject is missing)',
]);
expect($result['response'])->false();
expect($result['error'])->isInstanceOf(MailerError::class);
expect($result['error']->getOperation())->equals(MailerError::OPERATION_SEND);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Test\Mailer\Methods;
use MailPoet\Mailer\Methods\ErrorMappers\PHPMailMapper;
use MailPoet\Mailer\Methods\PHPMail;
class PHPMailTest extends \MailPoetTest {
@ -19,7 +20,8 @@ class PHPMailTest extends \MailPoetTest {
$this->mailer = new PHPMail(
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new PHPMailMapper()
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
@ -44,7 +46,8 @@ class PHPMailTest extends \MailPoetTest {
$mailer = new PHPMail(
$this->sender,
$this->reply_to,
$return_path = false
$return_path = false,
new PHPMailMapper()
);
expect($mailer->return_path)->equals($this->sender['from_email']);
}

View File

@ -3,6 +3,7 @@ namespace MailPoet\Test\Mailer\Methods;
use Helper\WordPressHooks as WPHooksHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\Methods\ErrorMappers\SMTPMapper;
use MailPoet\Mailer\Methods\SMTP;
use MailPoet\WP\Hooks;
@ -43,7 +44,8 @@ class SMTPTest extends \MailPoetTest {
$this->settings['encryption'],
$this->sender,
$this->reply_to,
$this->return_path
$this->return_path,
new SMTPMapper()
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
@ -82,7 +84,8 @@ class SMTPTest extends \MailPoetTest {
$this->settings['encryption'],
$this->sender,
$this->reply_to,
$return_path = false
$return_path = false,
new SMTPMapper()
);
expect($mailer->return_path)->equals($this->sender['from_email']);
}
@ -128,36 +131,6 @@ class SMTPTest extends \MailPoetTest {
expect($result['response'])->false();
}
function testItCanProcessExceptionMessage() {
$message = 'Connection could not be established with host localhost [Connection refused #111]' . PHP_EOL
. 'Log data:' . PHP_EOL
. '++ Starting Swift_SmtpTransport' . PHP_EOL
. '!! Connection could not be established with host localhost [Connection refused #111] (code: 0)';
expect($this->mailer->processExceptionMessage($message))
->equals('Connection could not be established with host localhost [Connection refused #111]');
}
function testItCanProcessLogMessageWhenOneExists() {
$message = '++ Swift_SmtpTransport started' . PHP_EOL
. '>> MAIL FROM:<moi@mrcasual.com>' . PHP_EOL
. '<< 250 OK' . PHP_EOL
. '>> RCPT TO:<test2@ietsdoenofferte.nl>' . PHP_EOL
. '<< 550 No such recipient here' . PHP_EOL
. '!! Expected response code 250/251/252 but got code "550", with message "550 No such recipient here' . PHP_EOL
. '" (code: 550)' . PHP_EOL
. '>> RSET' . PHP_EOL
. '<< 250 Reset OK' . PHP_EOL;
expect($this->mailer->processLogMessage('test@example.com', $extra_params = array(), $message))
->equals('Expected response code 250/251/252 but got code "550", with message "550 No such recipient here" (code: 550) Unprocessed subscriber: test@example.com');
expect($this->mailer->processLogMessage('test@example.com', $extra_params = array(), $message))
->equals('Expected response code 250/251/252 but got code "550", with message "550 No such recipient here" (code: 550) Unprocessed subscriber: test@example.com');
}
function testItReturnsGenericMessageWhenLogMessageDoesNotExist() {
expect($this->mailer->processLogMessage('test@example.com'))
->equals(Mailer::METHOD_SMTP . ' has returned an unknown error. Unprocessed subscriber: test@example.com');
}
function testItAppliesTransportFilter() {
$mailer = $this->mailer->buildMailer();
expect($mailer->getTransport()->getStreamOptions())->isEmpty();

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Test\Mailer\Methods;
use MailPoet\Mailer\Methods\ErrorMappers\SendGridMapper;
use MailPoet\Mailer\Methods\SendGrid;
class SendGridTest extends \MailPoetTest {
@ -24,7 +25,8 @@ class SendGridTest extends \MailPoetTest {
$this->mailer = new SendGrid(
$this->settings['api_key'],
$this->sender,
$this->reply_to
$this->reply_to,
new SendGridMapper()
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(