Merge pull request #784 from mailpoet/sending_svc_key_validation
Add sending service key validation [MAILPOET-743]
This commit is contained in:
67
lib/API/Endpoints/Services.php
Normal file
67
lib/API/Endpoints/Services.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Services extends APIEndpoint {
|
||||
public $bridge;
|
||||
|
||||
function __construct() {
|
||||
$this->bridge = new Bridge();
|
||||
}
|
||||
|
||||
function verifyMailPoetKey($data = array()) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if(!$key) {
|
||||
return $this->badRequest(array(
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkKey($key);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$success_message = null;
|
||||
if($state == Bridge::MAILPOET_KEY_VALID) {
|
||||
$success_message = __('Your MailPoet API key is valid!', 'mailpoet');
|
||||
} elseif($state == Bridge::MAILPOET_KEY_EXPIRING) {
|
||||
$success_message = sprintf(
|
||||
__('Your MailPoet key expires on %s!', 'mailpoet'),
|
||||
Carbon::createFromTimestamp(strtotime($result['data']['expire_at']))
|
||||
->format('Y-m-d')
|
||||
);
|
||||
}
|
||||
|
||||
if($success_message) {
|
||||
return $this->successResponse(array('message' => $success_message));
|
||||
}
|
||||
|
||||
switch($state) {
|
||||
case Bridge::MAILPOET_KEY_INVALID:
|
||||
$error = __('Your MailPoet key is invalid!', 'mailpoet');
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
$error = sprintf(
|
||||
__('Error validating API key, please try again later (code: %s)', 'mailpoet'),
|
||||
$code
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@@ -21,6 +22,12 @@ class Settings extends APIEndpoint {
|
||||
foreach($settings as $name => $value) {
|
||||
Setting::setValue($name, $value);
|
||||
}
|
||||
if(!empty($settings['mta']['mailpoet_api_key'])
|
||||
&& Bridge::isMPSendingServiceEnabled()
|
||||
) {
|
||||
$bridge = new Bridge();
|
||||
$bridge->checkKey($settings['mta']['mailpoet_api_key']);
|
||||
}
|
||||
return $this->successResponse(Setting::getAll());
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ class Menu {
|
||||
$this->assets_url = $assets_url;
|
||||
$subscribers_feature = new SubscribersFeature();
|
||||
$this->subscribers_over_limit = $subscribers_feature->check();
|
||||
$this->checkMailPoetAPIKey();
|
||||
}
|
||||
|
||||
function init() {
|
||||
@@ -387,6 +388,9 @@ class Menu {
|
||||
|
||||
function newsletters() {
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
if(isset($this->mp_api_key_valid) && $this->mp_api_key_valid === false) {
|
||||
return $this->displayMailPoetAPIKeyInvalidTemplate();
|
||||
}
|
||||
|
||||
global $wp_roles;
|
||||
|
||||
@@ -483,6 +487,39 @@ class Menu {
|
||||
exit;
|
||||
}
|
||||
|
||||
function displayMailPoetAPIKeyInvalidTemplate() {
|
||||
$this->displayPage('invalidkey.html', array(
|
||||
'subscriber_count' => Subscriber::getTotalSubscribers()
|
||||
));
|
||||
exit;
|
||||
}
|
||||
|
||||
static function isOnMailPoetAdminPage(array $exclude = null, $screen_id = null) {
|
||||
if(is_null($screen_id)) {
|
||||
if(empty($_REQUEST['page'])) {
|
||||
return false;
|
||||
}
|
||||
$screen_id = $_REQUEST['page'];
|
||||
}
|
||||
if(!empty($exclude)) {
|
||||
foreach($exclude as $slug) {
|
||||
if(stripos($screen_id, $slug) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (stripos($screen_id, 'mailpoet-') !== false);
|
||||
}
|
||||
|
||||
function checkMailPoetAPIKey(ServicesChecker $checker = null) {
|
||||
if(self::isOnMailPoetAdminPage()) {
|
||||
$show_notices = isset($_REQUEST['page'])
|
||||
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
|
||||
$checker = $checker ?: new ServicesChecker();
|
||||
$this->mp_api_key_valid = $checker->checkMailPoetAPIKeyValid($show_notices);
|
||||
}
|
||||
}
|
||||
|
||||
private function getLimitPerPage($model = null) {
|
||||
if($model === null) {
|
||||
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
|
||||
@@ -504,8 +541,4 @@ class Menu {
|
||||
$notice->displayWPNotice();
|
||||
}
|
||||
}
|
||||
|
||||
static function isOnMailPoetAdminPage() {
|
||||
return (!empty($_REQUEST['page']) && stripos($_REQUEST['page'], 'mailpoet-') !== false);
|
||||
}
|
||||
}
|
@@ -106,7 +106,7 @@ class Migrator {
|
||||
function sendingQueues() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'type varchar(12) NULL DEFAULT NULL,',
|
||||
'type varchar(90) NULL DEFAULT NULL,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'newsletter_rendered_body longtext,',
|
||||
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
|
||||
|
49
lib/Config/ServicesChecker.php
Normal file
49
lib/Config/ServicesChecker.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class ServicesChecker {
|
||||
function checkMailPoetAPIKeyValid($display_error_notice = true) {
|
||||
if(!Bridge::isMPSendingServiceEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = Setting::getValue(Bridge::API_KEY_STATE_SETTING_NAME);
|
||||
if(empty($result['state']) || $result['state'] == Bridge::MAILPOET_KEY_VALID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if($result['state'] == Bridge::MAILPOET_KEY_INVALID) {
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('All sending is currently paused! Your key to send with MailPoet is invalid. [link]Visit MailPoet.com to purchase a key[/link]', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
if($display_error_notice) {
|
||||
WPNotice::displayError($error);
|
||||
}
|
||||
return false;
|
||||
} elseif($result['state'] == Bridge::MAILPOET_KEY_EXPIRING
|
||||
&& !empty($result['data']['expire_at'])
|
||||
) {
|
||||
$date = date('Y-m-d', strtotime($result['data']['expire_at']));
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('Your newsletters are awesome! Don\'t forget to [link]upgrade your MailPoet email plan[/link] by %s to keep sending them to your subscribers.', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
$error = sprintf($error, $date);
|
||||
if($display_error_notice) {
|
||||
WPNotice::displayWarning($error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ namespace MailPoet\Cron;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
@@ -48,6 +49,7 @@ class Daemon {
|
||||
try {
|
||||
$this->executeScheduleWorker();
|
||||
$this->executeQueueWorker();
|
||||
$this->executeSendingServiceKeyCheckWorker();
|
||||
$this->executeBounceWorker();
|
||||
} catch(\Exception $e) {
|
||||
// continue processing, no need to handle errors
|
||||
@@ -80,6 +82,11 @@ class Daemon {
|
||||
return $queue->process();
|
||||
}
|
||||
|
||||
function executeSendingServiceKeyCheckWorker() {
|
||||
$worker = new SendingServiceKeyCheckWorker($this->timer);
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executeBounceWorker() {
|
||||
$bounce = new BounceWorker($this->timer);
|
||||
return $bounce->process();
|
||||
|
@@ -5,7 +5,9 @@ use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@@ -17,14 +19,24 @@ class WordPress {
|
||||
}
|
||||
|
||||
static function checkExecutionRequirements() {
|
||||
// sending queue
|
||||
$scheduled_queues = SchedulerWorker::getScheduledQueues();
|
||||
$running_queues = SendingQueueWorker::getRunningQueues();
|
||||
$sending_limit_reached = MailerLog::isSendingLimitReached();
|
||||
$bounce_sync_available = BounceWorker::checkBounceSyncAvailable();
|
||||
// sending service
|
||||
$mp_sending_enabled = Bridge::isMPSendingServiceEnabled();
|
||||
// bounce sync
|
||||
$bounce_due_queues = BounceWorker::getAllDueQueues();
|
||||
$bounce_future_queues = BounceWorker::getFutureQueues();
|
||||
return (($scheduled_queues || $running_queues) && !$sending_limit_reached)
|
||||
|| ($bounce_sync_available && ($bounce_due_queues || !$bounce_future_queues));
|
||||
// sending service key check
|
||||
$sskeycheck_due_queues = SendingServiceKeyCheckWorker::getAllDueQueues();
|
||||
$sskeycheck_future_queues = SendingServiceKeyCheckWorker::getFutureQueues();
|
||||
// check requirements for each worker
|
||||
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached);
|
||||
$bounce_sync_active = ($mp_sending_enabled && ($bounce_due_queues || !$bounce_future_queues));
|
||||
$sending_service_key_check_active = ($mp_sending_enabled && ($sskeycheck_due_queues || !$sskeycheck_future_queues));
|
||||
|
||||
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active);
|
||||
}
|
||||
|
||||
static function cleanup() {
|
||||
|
@@ -6,15 +6,18 @@ use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Bounce {
|
||||
const TASK_TYPE = 'bounce';
|
||||
const BATCH_SIZE = 100;
|
||||
|
||||
const BOUNCED_HARD = 'hard';
|
||||
const BOUNCED_SOFT = 'soft';
|
||||
const NOT_BOUNCED = null;
|
||||
const BATCH_SIZE = 100;
|
||||
|
||||
public $timer;
|
||||
public $api;
|
||||
@@ -25,12 +28,6 @@ class Bounce {
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
}
|
||||
|
||||
static function checkBounceSyncAvailable() {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
return !empty($mailer_config['method'])
|
||||
&& $mailer_config['method'] === Mailer::METHOD_MAILPOET;
|
||||
}
|
||||
|
||||
function initApi() {
|
||||
if(!$this->api) {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
@@ -39,7 +36,7 @@ class Bounce {
|
||||
}
|
||||
|
||||
function process() {
|
||||
if(!self::checkBounceSyncAvailable()) {
|
||||
if(!Bridge::isMPSendingServiceEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,7 +61,7 @@ class Bounce {
|
||||
}
|
||||
|
||||
static function scheduleBounceSync() {
|
||||
$already_scheduled = SendingQueue::where('type', 'bounce')
|
||||
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
@@ -72,7 +69,7 @@ class Bounce {
|
||||
return false;
|
||||
}
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = 'bounce';
|
||||
$queue->type = self::TASK_TYPE;
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->priority = SendingQueue::PRIORITY_LOW;
|
||||
$queue->scheduled_at = self::getNextRunDate();
|
||||
@@ -170,7 +167,7 @@ class Bounce {
|
||||
|
||||
static function getScheduledQueues($future = false) {
|
||||
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
|
||||
return SendingQueue::where('type', 'bounce')
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
@@ -178,7 +175,7 @@ class Bounce {
|
||||
}
|
||||
|
||||
static function getRunningQueues() {
|
||||
return SendingQueue::where('type', 'bounce')
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
|
146
lib/Cron/Workers/SendingServiceKeyCheck.php
Normal file
146
lib/Cron/Workers/SendingServiceKeyCheck.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class SendingServiceKeyCheck {
|
||||
const TASK_TYPE = 'sending_service_key_check';
|
||||
const UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT = 60;
|
||||
|
||||
public $timer;
|
||||
public $bridge;
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
}
|
||||
|
||||
function initApi() {
|
||||
if(!$this->bridge) {
|
||||
$this->bridge = new Bridge();
|
||||
}
|
||||
}
|
||||
|
||||
function process() {
|
||||
if(!Bridge::isMPSendingServiceEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->initApi();
|
||||
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
|
||||
if(!$scheduled_queues && !$running_queues) {
|
||||
self::schedule();
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($scheduled_queues as $i => $queue) {
|
||||
$this->prepareQueue($queue);
|
||||
}
|
||||
foreach($running_queues as $i => $queue) {
|
||||
$this->processQueue($queue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static function schedule() {
|
||||
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
if($already_scheduled) {
|
||||
return false;
|
||||
}
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = self::TASK_TYPE;
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->priority = SendingQueue::PRIORITY_LOW;
|
||||
$queue->scheduled_at = self::getNextRunDate();
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function prepareQueue(SendingQueue $queue) {
|
||||
$queue->status = null;
|
||||
$queue->save();
|
||||
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processQueue(SendingQueue $queue) {
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
|
||||
try {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
$result = $this->bridge->checkKey($mailer_config['mailpoet_api_key']);
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(empty($result['code']) || $result['code'] == Bridge::CHECK_ERROR_UNAVAILABLE) {
|
||||
// reschedule the check
|
||||
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$queue->scheduled_at = $scheduled_at->addMinutes(
|
||||
self::UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT
|
||||
);
|
||||
$queue->save();
|
||||
return false;
|
||||
}
|
||||
|
||||
$queue->processed_at = current_time('mysql');
|
||||
$queue->status = SendingQueue::STATUS_COMPLETED;
|
||||
$queue->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static function getNextRunDate() {
|
||||
$date = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
// Random day of the next week
|
||||
$date->setISODate($date->format('o'), $date->format('W') + 1, mt_rand(1, 7));
|
||||
$date->startOfDay();
|
||||
return $date;
|
||||
}
|
||||
|
||||
static function getScheduledQueues($future = false) {
|
||||
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getRunningQueues() {
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getAllDueQueues() {
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
return array_merge((array)$scheduled_queues, (array)$running_queues);
|
||||
}
|
||||
|
||||
static function getFutureQueues() {
|
||||
return self::getScheduledQueues(true);
|
||||
}
|
||||
}
|
@@ -2,22 +2,30 @@
|
||||
namespace MailPoet\Mailer\Methods;
|
||||
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class MailPoet {
|
||||
public $url = 'https://bridge.mailpoet.com/api/messages';
|
||||
public $url = 'https://bridge.mailpoet.com/api/v0/messages';
|
||||
public $api_key;
|
||||
public $sender;
|
||||
public $reply_to;
|
||||
public $services_checker;
|
||||
|
||||
function __construct($api_key, $sender, $reply_to) {
|
||||
$this->api_key = $api_key;
|
||||
$this->sender = $sender;
|
||||
$this->reply_to = $reply_to;
|
||||
$this->services_checker = new ServicesChecker(false);
|
||||
}
|
||||
|
||||
function send($newsletter, $subscriber, $extra_params = array()) {
|
||||
if($this->services_checker->checkMailPoetAPIKeyValid() === false) {
|
||||
$response = __('MailPoet API key is invalid!', 'mailpoet');
|
||||
return Mailer::formatMailerSendErrorResult($response);
|
||||
}
|
||||
$message_body = $this->getBody($newsletter, $subscriber);
|
||||
$result = wp_remote_post(
|
||||
$this->url,
|
||||
@@ -26,7 +34,11 @@ class MailPoet {
|
||||
if(is_wp_error($result)) {
|
||||
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
|
||||
}
|
||||
if(wp_remote_retrieve_response_code($result) !== 201) {
|
||||
$response_code = wp_remote_retrieve_response_code($result);
|
||||
if($response_code !== 201) {
|
||||
if($response_code === 401) {
|
||||
Bridge::invalidateKey();
|
||||
}
|
||||
$response = (wp_remote_retrieve_body($result)) ?
|
||||
wp_remote_retrieve_body($result) :
|
||||
wp_remote_retrieve_response_message($result);
|
||||
|
85
lib/Services/Bridge.php
Normal file
85
lib/Services/Bridge.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace MailPoet\Services;
|
||||
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Setting;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Bridge {
|
||||
const API_KEY_STATE_SETTING_NAME = 'mta.mailpoet_api_key_state';
|
||||
|
||||
const MAILPOET_KEY_VALID = 'valid';
|
||||
const MAILPOET_KEY_INVALID = 'invalid';
|
||||
const MAILPOET_KEY_EXPIRING = 'expiring';
|
||||
|
||||
const MAILPOET_KEY_CHECK_ERROR = 'check_error';
|
||||
|
||||
const CHECK_ERROR_UNAVAILABLE = 503;
|
||||
const CHECK_ERROR_UNKNOWN = 'unknown';
|
||||
|
||||
public $api;
|
||||
|
||||
static function isMPSendingServiceEnabled() {
|
||||
try {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
return !empty($mailer_config['method'])
|
||||
&& $mailer_config['method'] === Mailer::METHOD_MAILPOET;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function initApi($api_key) {
|
||||
if($this->api) {
|
||||
$this->api->setKey($api_key);
|
||||
} else {
|
||||
$this->api = new Bridge\API($api_key);
|
||||
}
|
||||
}
|
||||
|
||||
function checkKey($api_key) {
|
||||
$this->initApi($api_key);
|
||||
$result = $this->api->checkKey();
|
||||
return $this->processResult($result);
|
||||
}
|
||||
|
||||
function processResult(array $result) {
|
||||
$state_map = array(
|
||||
200 => self::MAILPOET_KEY_VALID,
|
||||
401 => self::MAILPOET_KEY_INVALID,
|
||||
402 => self::MAILPOET_KEY_EXPIRING
|
||||
);
|
||||
|
||||
$update_settings = false;
|
||||
|
||||
if(!empty($result['code']) && isset($state_map[$result['code']])) {
|
||||
$key_state = $state_map[$result['code']];
|
||||
$update_settings = true;
|
||||
} else {
|
||||
$key_state = self::MAILPOET_KEY_CHECK_ERROR;
|
||||
}
|
||||
|
||||
$state = array(
|
||||
'state' => $key_state,
|
||||
'data' => !empty($result['data']) ? $result['data'] : null,
|
||||
'code' => !empty($result['code']) ? $result['code'] : self::CHECK_ERROR_UNKNOWN
|
||||
);
|
||||
|
||||
if($update_settings) {
|
||||
Setting::setValue(
|
||||
self::API_KEY_STATE_SETTING_NAME,
|
||||
$state
|
||||
);
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
static function invalidateKey() {
|
||||
Setting::setValue(
|
||||
self::API_KEY_STATE_SETTING_NAME,
|
||||
array('state' => self::MAILPOET_KEY_INVALID)
|
||||
);
|
||||
}
|
||||
}
|
60
lib/Services/Bridge/API.php
Normal file
60
lib/Services/Bridge/API.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
public $url = 'https://bridge.mailpoet.com/api/v0/me';
|
||||
public $api_key;
|
||||
|
||||
function __construct($api_key) {
|
||||
$this->setKey($api_key);
|
||||
}
|
||||
|
||||
function checkKey() {
|
||||
$result = wp_remote_post(
|
||||
$this->url,
|
||||
$this->request(array('site' => home_url()))
|
||||
);
|
||||
return $this->processResponse($result);
|
||||
}
|
||||
|
||||
function setKey($api_key) {
|
||||
$this->api_key = $api_key;
|
||||
}
|
||||
|
||||
private function processResponse($result) {
|
||||
$code = wp_remote_retrieve_response_code($result);
|
||||
switch($code) {
|
||||
case 200:
|
||||
case 402:
|
||||
$body = json_decode(wp_remote_retrieve_body($result), true);
|
||||
break;
|
||||
case 401:
|
||||
$body = wp_remote_retrieve_body($result);
|
||||
break;
|
||||
default:
|
||||
$body = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return array('code' => $code, 'data' => $body);
|
||||
}
|
||||
|
||||
private function auth() {
|
||||
return 'Basic ' . base64_encode('api:' . $this->api_key);
|
||||
}
|
||||
|
||||
private function request($body) {
|
||||
return array(
|
||||
'timeout' => 10,
|
||||
'httpversion' => '1.0',
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => $this->auth()
|
||||
),
|
||||
'body' => json_encode($body)
|
||||
);
|
||||
}
|
||||
}
|
@@ -43,7 +43,7 @@ class Notice {
|
||||
}
|
||||
|
||||
function displayWPNotice() {
|
||||
$class = sprintf('notice notice-%s', $this->type);
|
||||
$class = sprintf('notice notice-%s mailpoet_notice_server', $this->type);
|
||||
$message = nl2br($this->message);
|
||||
|
||||
printf('<div class="%1$s"><p>%2$s</p></div>', $class, $message);
|
||||
|
65
tests/unit/API/Endpoints/ServicesTest.php
Normal file
65
tests/unit/API/Endpoints/ServicesTest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\API\Endpoints\Services;
|
||||
use MailPoet\API\Response as APIResponse;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
class ServicesTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->services_endpoint = new Services();
|
||||
$this->data = array('key' => '1234567890abcdef');
|
||||
}
|
||||
|
||||
function testItRespondsWithErrorIfNoKeyIsGiven() {
|
||||
$response = $this->services_endpoint->verifyMailPoetKey(array('key' => ''));
|
||||
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
|
||||
expect($response->errors[0]['message'])->equals('Please specify a key.');
|
||||
}
|
||||
|
||||
function testItRespondsWithSuccessIfKeyIsValid() {
|
||||
$this->services_endpoint->bridge = Stub::make(
|
||||
new Bridge(),
|
||||
array('checkKey' => array('state' => Bridge::MAILPOET_KEY_VALID)),
|
||||
$this
|
||||
);
|
||||
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
}
|
||||
|
||||
function testItRespondsWithErrorIfKeyIsInvalid() {
|
||||
$this->services_endpoint->bridge = Stub::make(
|
||||
new Bridge(),
|
||||
array('checkKey' => array('state' => Bridge::MAILPOET_KEY_INVALID)),
|
||||
$this
|
||||
);
|
||||
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
function testItRespondsWithErrorIfKeyIsExpiring() {
|
||||
$date = new DateTime;
|
||||
$this->services_endpoint->bridge = Stub::make(
|
||||
new Bridge(),
|
||||
array('checkKey' => array(
|
||||
'state' => Bridge::MAILPOET_KEY_EXPIRING,
|
||||
'data' => array('expire_at' => $date->format('c'))
|
||||
)),
|
||||
$this
|
||||
);
|
||||
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data['message'])->contains($date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
function testItRespondsWithErrorIfServiceIsUnavailable() {
|
||||
$this->services_endpoint->bridge = Stub::make(
|
||||
new Bridge(),
|
||||
array('checkKey' => array('code' => Bridge::CHECK_ERROR_UNAVAILABLE)),
|
||||
$this
|
||||
);
|
||||
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
|
||||
expect($response->errors[0]['message'])->contains((string)Bridge::CHECK_ERROR_UNAVAILABLE);
|
||||
}
|
||||
}
|
59
tests/unit/Config/MenuTest.php
Normal file
59
tests/unit/Config/MenuTest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
|
||||
class MenuTest extends MailPoetTest {
|
||||
function testItReturnsTrueIfCurrentPageBelongsToMailpoet() {
|
||||
$result = Menu::isOnMailPoetAdminPage(null, 'somepage');
|
||||
expect($result)->false();
|
||||
$result = Menu::isOnMailPoetAdminPage(null, 'mailpoet-newsletters');
|
||||
expect($result)->true();
|
||||
}
|
||||
|
||||
function testItRespectsExclusionsWhenCheckingMPPages() {
|
||||
$exclude = array('mailpoet-welcome');
|
||||
$result = Menu::isOnMailPoetAdminPage($exclude, 'mailpoet-welcome');
|
||||
expect($result)->false();
|
||||
$result = Menu::isOnMailPoetAdminPage($exclude, 'mailpoet-newsletters');
|
||||
expect($result)->true();
|
||||
}
|
||||
|
||||
function testItWorksWithRequestDataWhenCheckingMPPages() {
|
||||
$_REQUEST['page'] = 'mailpoet-newsletters';
|
||||
$result = Menu::isOnMailPoetAdminPage();
|
||||
expect($result)->true();
|
||||
|
||||
$_REQUEST['page'] = 'blah';
|
||||
$result = Menu::isOnMailPoetAdminPage();
|
||||
expect($result)->false();
|
||||
|
||||
unset($_REQUEST['page']);
|
||||
$result = Menu::isOnMailPoetAdminPage();
|
||||
expect($result)->false();
|
||||
}
|
||||
|
||||
function testItChecksMailpoetAPIKey() {
|
||||
$renderer = Stub::make(new Renderer());
|
||||
$assets_url = '';
|
||||
$menu = new Menu($renderer, $assets_url);
|
||||
|
||||
$_REQUEST['page'] = 'mailpoet-newsletters';
|
||||
$checker = Stub::make(
|
||||
new ServicesChecker(),
|
||||
array('checkMailPoetAPIKeyValid' => true),
|
||||
$this
|
||||
);
|
||||
$menu->checkMailPoetAPIKey($checker);
|
||||
expect($menu->mp_api_key_valid)->true();
|
||||
|
||||
$checker = Stub::make(
|
||||
new ServicesChecker(),
|
||||
array('checkMailPoetAPIKeyValid' => false),
|
||||
$this
|
||||
);
|
||||
$menu->checkMailPoetAPIKey($checker);
|
||||
expect($menu->mp_api_key_valid)->false();
|
||||
}
|
||||
}
|
71
tests/unit/Config/ServicesCheckerTest.php
Normal file
71
tests/unit/Config/ServicesCheckerTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
class ServicesCheckerTest extends MailPoetTest {
|
||||
function testItDoesNotCheckKeyIfMPSendingServiceIsDisabled() {
|
||||
$this->disableMailPoetSendingMethod();
|
||||
$result = ServicesChecker::checkMailPoetAPIKeyValid();
|
||||
expect($result)->null();
|
||||
}
|
||||
|
||||
function testItChecksKeyValidity() {
|
||||
$this->setMailPoetSendingMethod();
|
||||
Setting::setValue(
|
||||
Bridge::API_KEY_STATE_SETTING_NAME,
|
||||
array('state' => Bridge::MAILPOET_KEY_VALID)
|
||||
);
|
||||
$result = ServicesChecker::checkMailPoetAPIKeyValid();
|
||||
expect($result)->true();
|
||||
|
||||
Setting::setValue(
|
||||
Bridge::API_KEY_STATE_SETTING_NAME,
|
||||
array('state' => Bridge::MAILPOET_KEY_INVALID)
|
||||
);
|
||||
$result = ServicesChecker::checkMailPoetAPIKeyValid();
|
||||
expect($result)->false();
|
||||
|
||||
Setting::setValue(
|
||||
Bridge::API_KEY_STATE_SETTING_NAME,
|
||||
array(
|
||||
'state' => Bridge::MAILPOET_KEY_EXPIRING,
|
||||
'data' => array('expire_at' => date('c'))
|
||||
)
|
||||
);
|
||||
$result = ServicesChecker::checkMailPoetAPIKeyValid();
|
||||
expect($result)->true();
|
||||
|
||||
// unexpected state should be treated as valid
|
||||
Setting::setValue(
|
||||
Bridge::API_KEY_STATE_SETTING_NAME,
|
||||
array(
|
||||
'state' => 'unexpected'
|
||||
)
|
||||
);
|
||||
$result = ServicesChecker::checkMailPoetAPIKeyValid();
|
||||
expect($result)->true();
|
||||
}
|
||||
|
||||
|
||||
private function setMailPoetSendingMethod() {
|
||||
Setting::setValue(
|
||||
Mailer::MAILER_CONFIG_SETTING_NAME,
|
||||
array(
|
||||
'method' => 'MailPoet',
|
||||
'mailpoet_api_key' => 'some_key',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function disableMailPoetSendingMethod() {
|
||||
Setting::setValue(
|
||||
Mailer::MAILER_CONFIG_SETTING_NAME,
|
||||
array(
|
||||
'method' => 'PHPMail',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -29,8 +29,6 @@ class BounceTest extends MailPoetTest {
|
||||
|
||||
$this->bounce = new Bounce(microtime(true));
|
||||
|
||||
$api =
|
||||
|
||||
$this->bounce->api = new MailPoet\Cron\Workers\Bounce\MockAPI('key');
|
||||
}
|
||||
|
||||
@@ -42,12 +40,6 @@ class BounceTest extends MailPoetTest {
|
||||
expect(Bounce::BATCH_SIZE)->equals(100);
|
||||
}
|
||||
|
||||
function testItChecksIfCurrentSendingMethodIsMailpoet() {
|
||||
expect(Bounce::checkBounceSyncAvailable())->false();
|
||||
$this->setMailPoetSendingMethod();
|
||||
expect(Bounce::checkBounceSyncAvailable())->true();
|
||||
}
|
||||
|
||||
function testItThrowsExceptionWhenExecutionLimitIsReached() {
|
||||
try {
|
||||
$bounce = new Bounce(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT);
|
||||
|
199
tests/unit/Cron/Workers/SendingServiceKeyCheckTest.php
Normal file
199
tests/unit/Cron/Workers/SendingServiceKeyCheckTest.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SendingServiceKeyCheck;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
class SendingServiceKeyCheckTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->emails = array(
|
||||
'soft_bounce@example.com',
|
||||
'hard_bounce@example.com',
|
||||
'good_address@example.com'
|
||||
);
|
||||
|
||||
$this->sskeycheck = new SendingServiceKeyCheck(microtime(true));
|
||||
}
|
||||
|
||||
function testItConstructs() {
|
||||
expect($this->sskeycheck->timer)->notEmpty();
|
||||
}
|
||||
|
||||
function testItThrowsExceptionWhenExecutionLimitIsReached() {
|
||||
try {
|
||||
$sskeycheck = new SendingServiceKeyCheck(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT);
|
||||
self::fail('Maximum execution time limit exception was not thrown.');
|
||||
} catch(\Exception $e) {
|
||||
expect($e->getMessage())->equals('Maximum execution time has been reached.');
|
||||
}
|
||||
}
|
||||
|
||||
function testItSchedulesSendingServiceKeyCheck() {
|
||||
expect(SendingQueue::where('type', SendingServiceKeyCheck::TASK_TYPE)->findMany())->isEmpty();
|
||||
SendingServiceKeyCheck::schedule();
|
||||
expect(SendingQueue::where('type', SendingServiceKeyCheck::TASK_TYPE)->findMany())->notEmpty();
|
||||
}
|
||||
|
||||
function testItDoesNotScheduleSendingServiceKeyCheckTwice() {
|
||||
expect(count(SendingQueue::where('type', SendingServiceKeyCheck::TASK_TYPE)->findMany()))->equals(0);
|
||||
SendingServiceKeyCheck::schedule();
|
||||
expect(count(SendingQueue::where('type', SendingServiceKeyCheck::TASK_TYPE)->findMany()))->equals(1);
|
||||
SendingServiceKeyCheck::schedule();
|
||||
expect(count(SendingQueue::where('type', SendingServiceKeyCheck::TASK_TYPE)->findMany()))->equals(1);
|
||||
}
|
||||
|
||||
function testItCanGetScheduledQueues() {
|
||||
expect(SendingServiceKeyCheck::getScheduledQueues())->isEmpty();
|
||||
$this->createScheduledQueue();
|
||||
expect(SendingServiceKeyCheck::getScheduledQueues())->notEmpty();
|
||||
}
|
||||
|
||||
function testItCanGetRunningQueues() {
|
||||
expect(SendingServiceKeyCheck::getRunningQueues())->isEmpty();
|
||||
$this->createRunningQueue();
|
||||
expect(SendingServiceKeyCheck::getRunningQueues())->notEmpty();
|
||||
}
|
||||
|
||||
function testItCanGetAllDueQueues() {
|
||||
expect(SendingServiceKeyCheck::getAllDueQueues())->isEmpty();
|
||||
|
||||
// scheduled for now
|
||||
$this->createScheduledQueue();
|
||||
|
||||
// running
|
||||
$this->createRunningQueue();
|
||||
|
||||
// scheduled in the future (should not be retrieved)
|
||||
$queue = $this->createScheduledQueue();
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'))->addDays(7);
|
||||
$queue->save();
|
||||
|
||||
// completed (should not be retrieved)
|
||||
$queue = $this->createRunningQueue();
|
||||
$queue->status = SendingQueue::STATUS_COMPLETED;
|
||||
$queue->save();
|
||||
|
||||
expect(count(SendingServiceKeyCheck::getAllDueQueues()))->equals(2);
|
||||
}
|
||||
|
||||
function testItCanGetFutureQueues() {
|
||||
expect(SendingServiceKeyCheck::getFutureQueues())->isEmpty();
|
||||
$queue = $this->createScheduledQueue();
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'))->addDays(7);
|
||||
$queue->save();
|
||||
expect(count(SendingServiceKeyCheck::getFutureQueues()))->notEmpty();
|
||||
}
|
||||
|
||||
function testItFailsToProcessWithoutMailPoetMethodSetUp() {
|
||||
expect($this->sskeycheck->process())->false();
|
||||
}
|
||||
|
||||
function testItFailsToProcessWithoutQueues() {
|
||||
$this->setMailPoetSendingMethod();
|
||||
expect($this->sskeycheck->process())->false();
|
||||
}
|
||||
|
||||
function testItProcesses() {
|
||||
$this->setMailPoetSendingMethod();
|
||||
$this->createScheduledQueue();
|
||||
$this->createRunningQueue();
|
||||
expect($this->sskeycheck->process())->true();
|
||||
}
|
||||
|
||||
function testItPreparesSendingServiceKeyCheckQueue() {
|
||||
$queue = $this->createScheduledQueue();
|
||||
$this->sskeycheck->prepareQueue($queue);
|
||||
expect($queue->status)->null();
|
||||
}
|
||||
|
||||
function testItProcessesSendingServiceKeyCheckQueue() {
|
||||
$this->sskeycheck->bridge = Stub::make(
|
||||
new Bridge,
|
||||
array('checkKey' => array('code' => Bridge::MAILPOET_KEY_VALID)),
|
||||
$this
|
||||
);
|
||||
$this->setMailPoetSendingMethod();
|
||||
$queue = $this->createRunningQueue();
|
||||
$this->sskeycheck->prepareQueue($queue);
|
||||
$this->sskeycheck->processQueue($queue);
|
||||
expect($queue->status)->equals(SendingQueue::STATUS_COMPLETED);
|
||||
}
|
||||
|
||||
function testItReschedulesCheckOnException() {
|
||||
$this->sskeycheck->bridge = Stub::make(
|
||||
new Bridge,
|
||||
array('checkKey' => function () { throw new \Exception(); }),
|
||||
$this
|
||||
);
|
||||
$this->setMailPoetSendingMethod();
|
||||
$queue = $this->createRunningQueue();
|
||||
$scheduled_at = $queue->scheduled_at;
|
||||
$this->sskeycheck->prepareQueue($queue);
|
||||
$this->sskeycheck->processQueue($queue);
|
||||
expect($scheduled_at < $queue->scheduled_at)->true();
|
||||
}
|
||||
|
||||
function testItReschedulesCheckOnError() {
|
||||
$this->sskeycheck->bridge = Stub::make(
|
||||
new Bridge,
|
||||
array('checkKey' => array('code' => Bridge::CHECK_ERROR_UNAVAILABLE)),
|
||||
$this
|
||||
);
|
||||
$this->setMailPoetSendingMethod();
|
||||
$queue = $this->createRunningQueue();
|
||||
$scheduled_at = $queue->scheduled_at;
|
||||
$this->sskeycheck->prepareQueue($queue);
|
||||
$this->sskeycheck->processQueue($queue);
|
||||
expect($scheduled_at < $queue->scheduled_at)->true();
|
||||
}
|
||||
|
||||
function testItCalculatesNextRunDateWithinNextWeekBoundaries() {
|
||||
$current_date = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$next_run_date = SendingServiceKeyCheck::getNextRunDate();
|
||||
$difference = $next_run_date->diffInDays($current_date);
|
||||
// Subtract days left in the current week
|
||||
$difference -= (Carbon::DAYS_PER_WEEK - $current_date->format('N'));
|
||||
expect($difference)->lessOrEquals(7);
|
||||
expect($difference)->greaterOrEquals(0);
|
||||
}
|
||||
|
||||
private function setMailPoetSendingMethod() {
|
||||
Setting::setValue(
|
||||
Mailer::MAILER_CONFIG_SETTING_NAME,
|
||||
array(
|
||||
'method' => 'MailPoet',
|
||||
'mailpoet_api_key' => 'some_key',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function createScheduledQueue() {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = SendingServiceKeyCheck::TASK_TYPE;
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
private function createRunningQueue() {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = SendingServiceKeyCheck::TASK_TYPE;
|
||||
$queue->status = null;
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
}
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\Mailer\Methods\MailPoet;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
|
||||
class MailPoetAPITest extends MailPoetTest {
|
||||
function _before() {
|
||||
@@ -103,6 +105,21 @@ class MailPoetAPITest extends MailPoetTest {
|
||||
->equals('Basic ' . base64_encode('api:' . $this->settings['api_key']));
|
||||
}
|
||||
|
||||
function testItWillNotSendIfApiKeyIsMarkedInvalid() {
|
||||
if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
|
||||
$this->mailer->api_key = 'someapi';
|
||||
$this->mailer->services_checker = Stub::make(
|
||||
new ServicesChecker(),
|
||||
array('checkMailPoetAPIKeyValid' => false),
|
||||
$this
|
||||
);
|
||||
$result = $this->mailer->send(
|
||||
$this->newsletter,
|
||||
$this->subscriber
|
||||
);
|
||||
expect($result['response'])->false();
|
||||
}
|
||||
|
||||
function testItCannotSendWithoutProperApiKey() {
|
||||
if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
|
||||
$this->mailer->api_key = 'someapi';
|
||||
|
86
tests/unit/Services/BridgeTest.php
Normal file
86
tests/unit/Services/BridgeTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Services\Bridge\API;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Setting;
|
||||
|
||||
require_once('BridgeTestMockAPI.php');
|
||||
|
||||
class BridgeTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->valid_key = 'abcdefghijklmnopqrstuvwxyz';
|
||||
$this->invalid_key = '401' . $this->valid_key;
|
||||
$this->expiring_key = '402' . $this->valid_key;
|
||||
$this->uncheckable_key = '503' . $this->valid_key;
|
||||
|
||||
$this->bridge = new Bridge();
|
||||
|
||||
$this->bridge->api = new MailPoet\Services\Bridge\MockAPI('key');
|
||||
}
|
||||
|
||||
function testItChecksIfCurrentSendingMethodIsMailpoet() {
|
||||
expect(Bridge::isMPSendingServiceEnabled())->false();
|
||||
$this->setMailPoetSendingMethod();
|
||||
expect(Bridge::isMPSendingServiceEnabled())->true();
|
||||
}
|
||||
|
||||
function testMPCheckReturnsFalseWhenMailerThrowsException() {
|
||||
Setting::setValue(Mailer::MAILER_CONFIG_SETTING_NAME, '');
|
||||
expect(Bridge::isMPSendingServiceEnabled())->false();
|
||||
}
|
||||
|
||||
function testItInstantiatesDefaultAPI() {
|
||||
$this->bridge->api = null;
|
||||
$this->bridge->initApi(null);
|
||||
expect($this->bridge->api instanceof API)->true();
|
||||
}
|
||||
|
||||
function testItChecksValidKey() {
|
||||
$result = $this->bridge->checkKey($this->valid_key);
|
||||
expect($result)->notEmpty();
|
||||
expect($result['state'])->equals(Bridge::MAILPOET_KEY_VALID);
|
||||
|
||||
$result = $this->bridge->checkKey($this->invalid_key);
|
||||
expect($result)->notEmpty();
|
||||
expect($result['state'])->equals(Bridge::MAILPOET_KEY_INVALID);
|
||||
|
||||
$result = $this->bridge->checkKey($this->expiring_key);
|
||||
expect($result)->notEmpty();
|
||||
expect($result['state'])->equals(Bridge::MAILPOET_KEY_EXPIRING);
|
||||
expect($result['data']['expire_at'])->notEmpty();
|
||||
}
|
||||
|
||||
function testItReturnsErrorStateOnEmptyAPIResponseCode() {
|
||||
$api = Stub::make(new API(null), array('checkKey' => array()), $this);
|
||||
$this->bridge->api = $api;
|
||||
$result = $this->bridge->checkKey($this->valid_key);
|
||||
expect($result)->notEmpty();
|
||||
expect($result['state'])->equals(Bridge::MAILPOET_KEY_CHECK_ERROR);
|
||||
}
|
||||
|
||||
function testItInvalidatesKey() {
|
||||
Setting::setValue(
|
||||
Bridge::API_KEY_STATE_SETTING_NAME,
|
||||
array('state' => Bridge::MAILPOET_KEY_VALID)
|
||||
);
|
||||
Bridge::invalidateKey();
|
||||
$value = Setting::getValue(Bridge::API_KEY_STATE_SETTING_NAME);
|
||||
expect($value)->equals(array('state' => Bridge::MAILPOET_KEY_INVALID));
|
||||
}
|
||||
|
||||
private function setMailPoetSendingMethod() {
|
||||
Setting::setValue(
|
||||
Mailer::MAILER_CONFIG_SETTING_NAME,
|
||||
array(
|
||||
'method' => 'MailPoet',
|
||||
'mailpoet_api_key' => 'some_key',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
|
||||
}
|
||||
}
|
46
tests/unit/Services/BridgeTestMockAPI.php
Normal file
46
tests/unit/Services/BridgeTestMockAPI.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace MailPoet\Services\Bridge;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class MockAPI {
|
||||
public $api_key;
|
||||
|
||||
function __construct($api_key) {
|
||||
$this->setKey($api_key);
|
||||
}
|
||||
|
||||
function checkKey() {
|
||||
// if key begins with these codes, return them
|
||||
$regex = '/^(401|402|503)/';
|
||||
$code = preg_match($regex, $this->api_key, $m) ? $m[1] : 200;
|
||||
return $this->processResponse($code);
|
||||
}
|
||||
|
||||
function setKey($api_key) {
|
||||
$this->api_key = $api_key;
|
||||
}
|
||||
|
||||
private function processResponse($code) {
|
||||
switch($code) {
|
||||
case 200:
|
||||
$body = array('subscriber_limit' => 10000);
|
||||
break;
|
||||
case 402:
|
||||
$body = array(
|
||||
'subscriber_limit' => 10000,
|
||||
'expire_at' => Carbon::createFromTimestamp(current_time('timestamp'))
|
||||
->addMonth()->format('c')
|
||||
);
|
||||
break;
|
||||
case 401:
|
||||
default:
|
||||
$body = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return array('code' => $code, 'data' => $body);
|
||||
}
|
||||
}
|
16
views/invalidkey.html
Normal file
16
views/invalidkey.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<% extends 'layout.html' %>
|
||||
|
||||
<% block content %>
|
||||
|
||||
<div class="wrap about-wrap">
|
||||
<h1><%= __("All sending is currently paused!") %></h1>
|
||||
|
||||
<p class="about-text">
|
||||
<%= __("Your key to send with MailPoet is invalid.") %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a class="button button-primary" target="_blank" href="https://account.mailpoet.com?s=<%= subscriber_count %>"><%= __('Visit MailPoet.com to purchase a key') %></a>
|
||||
</p>
|
||||
</div>
|
||||
<% endblock %>
|
@@ -150,6 +150,10 @@
|
||||
name="mta[mailpoet_api_key]"
|
||||
value="<%=- settings.mta.mailpoet_api_key -%>"
|
||||
/>
|
||||
<a
|
||||
id="mailpoet_api_key_verify"
|
||||
class="button-secondary"
|
||||
><%= __('Verify') %></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -687,7 +691,7 @@
|
||||
if(settings.sender.address.length === 0) {
|
||||
// validation
|
||||
return MailPoet.Notice.error(
|
||||
'The email could not be sent. Make sure the option "Email notifications" has a FROM email address in the Basics tab.',
|
||||
'<%= __('The email could not be sent. Make sure the option "Email notifications" has a FROM email address in the Basics tab.') %>',
|
||||
{ scroll: true, static: true }
|
||||
);
|
||||
}
|
||||
@@ -727,6 +731,45 @@
|
||||
});
|
||||
});
|
||||
|
||||
// verifying api key
|
||||
$('#mailpoet_api_key_verify').on('click', function() {
|
||||
// get api key
|
||||
var key = $('#mailpoet_api_key').val();
|
||||
|
||||
if(key.length === 0) {
|
||||
// validation
|
||||
return MailPoet.Notice.error(
|
||||
'<%= __('Please specify an API key before validating it.') %>',
|
||||
{ scroll: true, static: true }
|
||||
);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'services',
|
||||
action: 'verifyMailPoetKey',
|
||||
data: {
|
||||
key: key
|
||||
}
|
||||
}).always(function() {
|
||||
MailPoet.Modal.loading(false);
|
||||
}).done(function(response) {
|
||||
// Hide server error notices
|
||||
$('.mailpoet_notice_server').hide();
|
||||
MailPoet.Notice.success(
|
||||
response.data.message,
|
||||
{ scroll: true }
|
||||
);
|
||||
}).fail(function(response) {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function(error) { return error.message; }),
|
||||
{ scroll: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// sending frequency update based on selected provider
|
||||
$('#mailpoet_smtp_provider').on('change keyup', setProviderForm);
|
||||
$('#mailpoet_web_host').on('change keyup', renderHostSendingFrequency);
|
||||
@@ -825,6 +868,14 @@
|
||||
var method = getMethodFromGroup(group);
|
||||
|
||||
$('#mta_method').val(method);
|
||||
|
||||
if(group === 'mailpoet') {
|
||||
// Verify key on saving
|
||||
$('#mailpoet_api_key_verify').trigger('click');
|
||||
} else {
|
||||
// Hide server error notices
|
||||
$('.mailpoet_notice_server').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getMethodFromGroup(group) {
|
||||
|
Reference in New Issue
Block a user