- Refactors Mailer class

- Refactors SendingQueue worker class
- Adds Maier router with a send() method + ability to specify sending method
- Updates tests
- Introduces 'stopping' and 'starting' cron states
- Improves cron control mechanism
Closes #276
This commit is contained in:
Vlad
2016-01-05 10:34:57 -05:00
parent 112fe0cd6e
commit f1bf2bb097
30 changed files with 1202 additions and 1433 deletions

View File

@ -15,10 +15,10 @@ define(
status: 'loading'
};
},
getDaemonData: function() {
getCronData: function() {
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'getDaemonStatus'
action: 'getStatus'
})
.done(function(response) {
jQuery('.button-primary')
@ -32,25 +32,23 @@ define(
},
componentDidMount: function() {
if(this.isMounted()) {
this.getDaemonData();
setInterval(this.getDaemonData, 5000);
this.getCronData();
setInterval(this.getCronData, 5000);
}
},
controlDaemon: function(action) {
controlCron: function(action) {
if (jQuery('.button-primary').hasClass('disabled')) {
return;
}
jQuery('.button-primary')
.addClass('disabled');
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'controlDaemon',
data: {
'action': action
}
action: action,
})
.done(function(response) {
if(!response.result) {
//this.replaceState();
} else {
//this.setState(response);
MailPoet.Notice.error(MailPoetI18n.daemonControlError);
}
}.bind(this));
},
@ -71,19 +69,25 @@ define(
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'stop')}>Stop</a>&nbsp;&nbsp;
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'pause')}>Pause</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
</div>
);
break;
case 'starting':
case 'stopping':
return(
<div>
Daemon is {this.state.status}
</div>
);
break;
case 'paused':
case 'stopped':
return(
<div>
Daemon is {this.state.status}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'start')}>Start</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a>
</div>
);
break;

View File

@ -16,7 +16,7 @@ define(
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var settings = window.mailpoet_settings || {};
var fields = [
{
@ -24,14 +24,17 @@ define(
label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.",
type: 'text'
type: 'text',
validation: {
'data-parsley-required': true
}
},
{
name: 'segments',
label: 'Lists',
tip: "The subscriber list that will be used for this campaign.",
label: 'Segments',
tip: "The subscriber segment that will be used for this campaign.",
type: 'selection',
placeholder: "Select a list",
placeholder: "Select a segment",
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
@ -111,12 +114,19 @@ define(
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val()
segments: jQuery('#mailpoet_segments').val(),
sender: {
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
},
reply_to: {
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
}
}
}).done(function(response) {
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter is being sent...'
);

View File

@ -75,7 +75,7 @@ class Initializer {
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUE_TABLE', $sending_queues);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
}
@ -113,7 +113,6 @@ class Initializer {
}
function setupAnalytics() {
$widget = new Analytics();
$widget->init();
}
@ -143,10 +142,12 @@ class Initializer {
}
function runQueueSupervisor() {
if (php_sapi_name() === 'cli') return;
try {
$supervisor = new Supervisor();
$supervisor->checkDaemon();
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
function setupImages() {

View File

@ -8,14 +8,14 @@ if(!defined('ABSPATH')) exit;
class PublicAPI {
function __construct() {
# http://example.com/?mailpoet-api&section=&action=&payload=
# http://example.com/?mailpoet-api&section=&action=&request_payload=
$this->api = isset($_GET['mailpoet-api']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false;
$this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->payload = isset($_GET['payload']) ?
json_decode(urldecode($_GET['payload']), true) :
$this->requestPayload = isset($_GET['request_payload']) ?
json_decode(urldecode($_GET['request_payload']), true) :
false;
}
@ -26,10 +26,9 @@ class PublicAPI {
function queue() {
try {
$queue = new Daemon($this->payload);
$queue = new Daemon($this->requestPayload);
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
// mailer configuration error
}
}

View File

@ -10,18 +10,18 @@ require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Daemon {
function __construct($payload = array()) {
function __construct($requestPayload = array()) {
set_time_limit(0);
ignore_user_abort();
list ($this->daemon, $this->daemonData) = $this->getDaemon();
$this->refreshedToken = $this->refreshToken();
$this->payload = $payload;
$this->requestPayload = $requestPayload;
$this->timer = microtime(true);
}
function start() {
if(!isset($this->payload['session'])) {
$this->abortWithError('missing session ID');
if(!isset($this->requestPayload['session'])) {
$this->abortWithError(__('Missing session ID.'));
}
$this->manageSession('start');
$daemon = $this->daemon;
@ -30,58 +30,63 @@ class Daemon {
$daemon = Setting::create();
$daemon->name = 'cron_daemon';
$daemonData = array(
'status' => null,
'status' => 'starting',
'counter' => 0
);
$daemon->value = json_encode($daemonData);
$daemon->save();
}
if($daemonData['status'] !== 'started') {
if($daemonData['status'] === 'started') {
$_SESSION['cron_daemon'] = array(
'result' => false,
'errors' => array(__('Daemon already running.'))
);
}
if($daemonData['status'] === 'starting') {
$_SESSION['cron_daemon'] = 'started';
$_SESSION['cron_daemon'] = array('result' => true);
$daemonData['status'] = 'started';
$daemonData['token'] = $this->refreshedToken;
$_SESSION['cron_daemon'] = array('result' => true);
$this->manageSession('end');
$daemon->value = json_encode($daemonData);
$daemon->save();
$this->callSelf();
} else {
$_SESSION['cron_daemon'] = array(
'result' => false,
'error' => 'already started'
);
}
$this->manageSession('end');
}
function run() {
if(!$this->daemon || $this->daemonData['status'] !== 'started') {
$this->abortWithError('not running');
$allowedStatuses = array(
'stopping',
'starting',
'started'
);
if(!$this->daemon || !in_array($this->daemonData['status'], $allowedStatuses)) {
$this->abortWithError(__('Invalid daemon status.'));
}
if(!isset($this->payload['token']) ||
$this->payload['token'] !== $this->daemonData['token']
if(!isset($this->requestPayload['token']) ||
$this->requestPayload['token'] !== $this->daemonData['token']
) {
$this->abortWithError('invalid token');
$this->abortWithError('Invalid token.');
}
try {
$sendingQueue = new SendingQueue($this->timer);
$sendingQueue->process();
} catch(Exception $e) {
}
$elapsedTime = microtime(true) - $this->timer;
if($elapsedTime < 30) {
sleep(30 - $elapsedTime);
}
// after each execution, read daemon in case it's status was modified
list($daemon, $daemonData) = $this->getDaemon();
$daemonData['counter']++;
if($daemonData['status'] === 'stopping') $daemonData['status'] = 'stopped';
if($daemonData['status'] === 'starting') $daemonData['status'] = 'started';
$daemonData['token'] = $this->refreshedToken;
$daemonData['counter']++;
$daemon->value = json_encode($daemonData);
$daemon->save();
if($daemonData['status'] === 'strated') $this->callSelf();
if($daemonData['status'] === 'started') $this->callSelf();
}
function getDaemon() {
@ -103,7 +108,7 @@ class Daemon {
if(session_id()) {
session_write_close();
}
session_id($this->payload['session']);
session_id($this->requestPayload['session']);
session_start();
break;
case 'end':
@ -114,9 +119,8 @@ class Daemon {
function callSelf() {
$payload = json_encode(array('token' => $this->refreshedToken));
Supervisor::getRemoteUrl(
'/?mailpoet-api&section=queue&action=run&payload=' . urlencode($payload)
Supervisor::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=run&request_payload=' . urlencode($payload)
);
exit;
}
@ -125,7 +129,7 @@ class Daemon {
wp_send_json(
array(
'result' => false,
'error' => $error
'errors' => array($error)
));
exit;
}

View File

@ -11,7 +11,7 @@ class Supervisor {
function __construct($forceStart = false) {
$this->forceStart = $forceStart;
if(!Env::isPluginActivated()) {
throw new \Exception('Database has not been configured.');
throw new \Exception(__('MailPoet is not activated.'));
}
list ($this->daemon, $this->daemonData) = $this->getDaemon();
}
@ -20,17 +20,24 @@ class Supervisor {
if(!$this->daemon) {
return $this->startDaemon();
}
if(!$this->forceStart && $this->daemonData['status'] === 'stopped') {
if(!$this->forceStart && (
$this->daemonData['status'] === 'stopped' ||
$this->daemonData['status'] === 'stopping')
) {
return $this->daemonData['status'];
}
$timeSinceLastRun = $this->getDaemonLastRunTime();
if($timeSinceLastRun < 40) {
if(!$this->forceStart) {
return;
}
$currentTime = Carbon::now('UTC');
$lastUpdateTime = Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at, 'UTC'
);
$timeSinceLastStart = $currentTime->diffInSeconds($lastUpdateTime);
if($timeSinceLastStart < 40) return;
$this->daemonData['status'] = null;
if($this->daemonData['status'] === 'stopping' ||
$this->daemonData['status'] === 'starting'
) {
return $this->daemonData['status'];
}
}
$this->daemonData['status'] = 'starting';
$this->daemon->value = json_encode($this->daemonData);
$this->daemon->save();
return $this->startDaemon();
@ -41,9 +48,9 @@ class Supervisor {
$sessionId = session_id();
session_write_close();
$_SESSION['cron_daemon'] = null;
$payload = json_encode(array('session' => $sessionId));
self::getRemoteUrl(
'/?mailpoet-api&section=queue&action=start&payload=' . urlencode($payload)
$requestPayload = json_encode(array('session' => $sessionId));
self::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=start&request_payload=' . urlencode($requestPayload)
);
session_start();
$daemonStatus = $_SESSION['cron_daemon'];
@ -62,10 +69,10 @@ class Supervisor {
);
}
static function getRemoteUrl($url) {
static function accessRemoteUrl($url) {
$args = array(
'timeout' => 1,
'user-agent' => 'MailPoet (www.mailpoet.com)'
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
);
wp_remote_get(
self::getSiteUrl() . $url,
@ -74,11 +81,28 @@ class Supervisor {
}
static function getSiteUrl() {
if(preg_match('!:\d+/!', site_url())) return site_url();
preg_match('!http://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// additional check for some sites running on a virtual machine or behind
// proxy where there could be different ports (e.g., host:8080 => guest:80)
// if the site URL does not contain a port, return the URL
if(!preg_match('!^https?://.*?:\d+!', site_url())) return site_url();
preg_match('!://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// connect to the URL with port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
return ($fp) ?
site_url() :
preg_replace('/(?=:\d+):\d+/', '$1', site_url());
if($fp) return site_url();
// connect to the URL without port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connections fail
throw new \Exception(__('Site URL is unreachable.'));
}
function getDaemonLastRunTime() {
$currentTime = Carbon::now('UTC');
$lastUpdateTime = Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at, 'UTC'
);
return $currentTime->diffInSeconds($lastUpdateTime);
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace MailPoet\Cron\Workers;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Router\Mailer;
if(!defined('ABSPATH')) exit;
@ -15,75 +15,58 @@ class SendingQueue {
}
function process() {
$queues =
\MailPoet\Models\SendingQueue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
foreach($queues as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id);
foreach($this->getQueues() as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id)
->asArray();
if(!$newsletter) {
continue;
};
$newsletter = $newsletter->asArray();
$mailer = new Mailer($httpRequest = false);
if(!empty($newsletter['sender_address']) &&
!empty($newsletter['sender_name'])
) {
$mailer->fromName = $newsletter['sender_name'];
$mailer->fromEmail = $newsletter['sender_address'];
$mailer->fromNameEmail = sprintf(
'%s <%s>',
$mailer->fromName,
$mailer->fromEmail
);
}
if(!empty($newsletter['reply_to_address']) &&
!empty($newsletter['reply_to_name'])
) {
$mailer->replyToName = $newsletter['reply_to_name'];
$mailer->replyToEmail = $newsletter['reply_to_address'];
$mailer->replyToNameEmail = sprintf(
'%s <%s>',
$mailer->replyToName,
$mailer->replyToEmail
);
}
$mailer->mailer = $mailer->buildMailer();
$renderer = new Renderer(json_decode($newsletter['body'], true));
$newsletter = array(
'subject' => $newsletter['subject'],
'id' => $newsletter['id'],
'body' => array(
'html' => $renderer->renderAll(),
'text' => ''
// TODO: add text body
)
);
$newsletter = $this->renderNewsletter($newsletter);
$mailer = $this->configureMailerForNewsletter($newsletter);
$subscribers = json_decode($queue->subscribers, true);
$subscribersToProcess = $subscribers['to_process'];
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
foreach(array_chunk($subscribersToProcess, 200) as $subscriberIds) {
$dbSubscribers = Subscriber::whereIn('id', $subscriberIds)
->findArray();
foreach($dbSubscribers as $i => $dbSubscriber) {
foreach($dbSubscribers as $dbSubscriber) {
$this->checkExecutionTimer();
// TODO: replace shortcodes in the newsletter
$result = $mailer->mailer->send(
$newsletter,
$mailer->transformSubscriber($dbSubscriber)
);
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $dbSubscriber['id'];
$newsletterStatistics->newsletter_id = $newsletter['id'];
$newsletterStatistics->queue_id = $queue->id;
$newsletterStatistics->save();
$result = $this->sendNewsletter(
$mailer,
$this->processNewsletter($newsletter),
$dbSubscriber);
if($result) {
$this->updateStatistics($newsletter['id'], $dbSubscriber['id'], $queue->id);
$subscribers['processed'][] = $dbSubscriber['id'];
} else {
$subscribers['failed'][] = $dbSubscriber['id'];
} else $subscribers['failed'][] = $dbSubscriber['id'];
$this->updateQueue($queue, $subscribers);
}
}
}
}
function processNewsletter($newsletter) {
// TODO: replace shortcodes, etc..
return $newsletter;
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
return $mailer->mailerInstance->send(
$newsletter,
$mailer->transformSubscriber($subscriber)
);
}
function updateStatistics($newsletterId, $subscriberId, $queueId) {
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $newsletterId;
$newsletterStatistics->newsletter_id = $subscriberId;
$newsletterStatistics->queue_id = $queueId;
$newsletterStatistics->save();
}
function updateQueue($queue, $subscribers) {
$subscribers['to_process'] = array_values(
array_diff(
$subscribers['to_process'],
@ -103,12 +86,39 @@ class SendingQueue {
$queue->subscribers = json_encode($subscribers);
$queue->save();
}
}
}
function configureMailerForNewsletter($newsletter) {
if(!empty($newsletter['sender_address']) && !empty($newsletter['sender_name'])) {
$sender = array(
'name' => $newsletter['sender_name'],
'address' => $newsletter['sender_address']
);
} else $sender = false;
if(!empty($newsletter['reply_to_address']) && !empty($newsletter['reply_to_name'])) {
$replyTo = array(
'name' => $newsletter['reply_to_name'],
'address' => $newsletter['reply_to_address']
);
} else $replyTo = false;
$mailer = new Mailer($method = false, $sender, $replyTo);
return $mailer;
}
function checkExecutionTimer() {
$elapsedTime = microtime(true) - $this->timer;
if($elapsedTime >= 28) throw new \Exception('Maximum execution time reached.');
if($elapsedTime >= 30) throw new \Exception('Maximum execution time reached.');
}
function getQueues() {
return \MailPoet\Models\SendingQueue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
}
function renderNewsletter($newsletter) {
$renderer = new Renderer(json_decode($newsletter['body'], true));
$newsletter['body'] = $renderer->renderAll();
return $newsletter;
}
}

141
lib/Mailer/Mailer.php Normal file
View File

@ -0,0 +1,141 @@
<?php
namespace MailPoet\Mailer;
use MailPoet\Models\Setting;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Mailer {
function __construct($mailer = false, $sender = false, $reply_to = false) {
$this->mailer = $this->getMailer($mailer);
$this->sender = $this->getSender($sender);
$this->replyTo = $this->getReplyTo($reply_to);
$this->mailerInstance = $this->buildMailer();
}
function send($newsletter, $subscriber) {
$subscriber = $this->transformSubscriber($subscriber);
return $this->mailerInstance->send($newsletter, $subscriber);
}
function buildMailer() {
switch($this->mailer['method']) {
case 'AmazonSES':
$mailerInstance = new $this->mailer['class'](
$this->mailer['region'],
$this->mailer['access_key'],
$this->mailer['secret_key'],
$this->sender['fromNameEmail']
);
break;
case 'ElasticEmail':
$mailerInstance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
case 'MailGun':
$mailerInstance = new $this->mailer['class'](
$this->mailer['domain'],
$this->mailer['api_key'],
$this->sender['fromNameEmail']
);
break;
case 'MailPoet':
$mailerInstance = new $this->mailer['class'](
$this->mailer['mailpoet_api_key'],
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
case 'Mandrill':
$mailerInstance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
case 'SendGrid':
$mailerInstance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
case 'WPMail':
$mailerInstance = new $this->mailer['class'](
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
case 'SMTP':
$mailerInstance = new $this->mailer['class'](
$this->mailer['host'],
$this->mailer['port'],
$this->mailer['authentication'],
$this->mailer['login'],
$this->mailer['password'],
$this->mailer['encryption'],
$this->sender['fromEmail'],
$this->sender['fromName']
);
break;
default:
throw new \Exception(__('Mailing method does not exist.'));
break;
}
return $mailerInstance;
}
function getMailer($mailer = false) {
if(!$mailer) {
$mailer = Setting::getValue('mta', null);
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured.'));
}
$mailer['class'] = 'MailPoet\\Mailer\\Methods\\' . $mailer['method'];
return $mailer;
}
function getSender($sender = false) {
if(!$sender) {
$sender = Setting::getValue('sender', null);
if(!$sender) throw new \Exception(__('Sender name and email are not configured.'));
}
return array(
'fromName' => $sender['name'],
'fromEmail' => $sender['address'],
'fromNameEmail' => sprintf('%s <%s>', $sender['name'], $sender['address'])
);
}
function getReplyTo($replyTo = false) {
if(!$replyTo) {
$replyTo = Setting::getValue('replyTo', null);
if(!$replyTo) {
$replyTo = array(
'name' => $this->sender['fromName'],
'address' => $this->sender['fromEmail']
);
}
}
return array(
'replyToName' => $replyTo['name'],
'replyToEmail' => $replyTo['address'],
'replyToNameEmail' => sprintf('%s <%s>', $replyTo['name'], $replyTo['address'])
);
}
function transformSubscriber($subscriber) {
if(!is_array($subscriber)) return $subscriber;
if(isset($subscriber['address'])) $subscriber['email'] = $subscriber['address'];
$first_name = (isset($subscriber['first_name'])) ? $subscriber['first_name'] : '';
$last_name = (isset($subscriber['last_name'])) ? $subscriber['last_name'] : '';
if(!$first_name && !$last_name) return $subscriber['email'];
$subscriber = sprintf('%s %s <%s>', $first_name, $last_name, $subscriber['email']);
$subscriber = trim(preg_replace('!\s\s+!', ' ', $subscriber));
return $subscriber;
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer\API;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer\API;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer\API;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer\API;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,5 @@
<?php
namespace MailPoet\Mailer\API;
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Mailer;
namespace MailPoet\Mailer\Methods;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;

View File

@ -4,7 +4,7 @@ namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class SendingQueue extends Model {
public static $_table = MP_SENDING_QUEUE_TABLE;
public static $_table = MP_SENDING_QUEUES_TABLE;
function __construct() {
parent::__construct();

View File

@ -1,36 +1,29 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers;
use MailPoet\Cron\Daemon;
use MailPoet\Cron\Supervisor;
if(!defined('ABSPATH')) exit;
class Cron {
function controlDaemon($data) {
switch($data['action']) {
case 'start':
$supervisor = new \MailPoet\Cron\Supervisor($forceStart = true);
function start() {
$supervisor = new Supervisor($forceStart = true);
wp_send_json(
array(
'result' => $supervisor->checkDaemon()
'result' => $supervisor->checkDaemon() ? true : false
)
);
exit;
break;
case 'stop':
$status = 'stopped';
break;
default:
$status = 'paused';
break;
}
$daemon = new \MailPoet\Cron\Daemon();
if(!$daemon->daemon || $daemon->daemonData['status'] !== 'started') {
function stop() {
$daemon = new Daemon();
if(!$daemon->daemon ||
$daemon->daemonData['status'] !== 'started'
) {
$result = false;
} else {
$daemon->daemonData['status'] = $status;
$daemon->daemonData['status'] = 'stopping';
$daemon->daemon->value = json_encode($daemon->daemonData);
$result = $daemon->daemon->save();
}
@ -41,153 +34,8 @@ class Cron {
);
}
function getDaemonStatus() {
function getStatus() {
$daemon = new \MailPoet\Cron\BootStrapMenu();
wp_send_json($daemon->bootStrap());
}
function addQueue($data) {
$queue = SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
!d($queue);
exit;
$queue = SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()
->findArray(),
'id'
));
}
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = json_encode(
array(
'to_process' => $subscriber_ids
)
);
$queue->count_total = $queue->count_to_process = count($subscriber_ids);
$queue->save();
wp_send_json(
!$queue->save() ?
array(
'result' => false,
'error' => 'Queue could not be created.'
) :
array(
'result' => true,
'data' => array($queue->id)
)
);
}
function addQueues($data) {
$result = array_map(function ($queueData) {
$queue = SendingQueue::create();
$queue->newsletter_id = $queueData['newsletter_id'];
$queue->subscribers = json_encode(
array(
'to_process' => $queueData['subscribers']
)
);
$queue->count_total = $queue->count_to_process = count($queueData['subscribers']);
$queue->save();
return array(
'newsletter_id' => $queue->newsletter_id,
'queue_id' => $queue->id
);
}, $data);
$result = Helpers::arrayColumn($result, 'queue_id', 'newsletter_id');
wp_send_json(
count($data) != count($result) ?
array(
'result' => false,
'error' => __('Some queues could not be created.'),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'error' => __('Queue not found.')
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'error' => __('Queues not found.')
)
);
}
foreach($queues as $queue) {
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
}
wp_send_json(array('result' => true));
}
function getQueueStatus($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

View File

@ -1,191 +1,20 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Setting;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Mailer {
function __construct($httpRequest = true) {
$this->mailerType = array(
'AmazonSES' => 'API',
'ElasticEmail' => 'API',
'MailGun' => 'API',
'Mandrill' => 'API',
'SendGrid' => 'API',
'MailPoet' => null,
'SMTP' => null,
'WPMail' => null
);
if(!$httpRequest) {
list($this->fromName, $this->fromEmail, $this->fromNameEmail)
= $this->getSetting('sender');
$this->mailer = $this->getSetting('mailer');
}
}
function send($data) {
$subscriber = $this->transformSubscriber($data['subscriber']);
list($fromName, $fromEmail, $fromNameEmail)
= $this->getSetting('sender');
if(!$fromName && !$fromEmail) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Please configure your name and e-mail address.'))
)
$mailer = new \MailPoet\Mailer\Mailer(
(isset($data['mailer'])) ? $data['mailer'] : false,
(isset($data['sender'])) ? $data['sender'] : false,
(isset($data['reply_to'])) ? $data['reply_to'] : false
);
}
$data['mailer']['class'] = 'MailPoet\\Mailer\\' .
(($this->mailerType[$data['mailer']['method']]) ?
$this->mailerType[$data['mailer']['method']] . '\\' . $data['mailer']['method'] :
$data['mailer']['method']
);
$mailer = $this->buildMailer(
$data['mailer'],
$fromName,
$fromEmail,
$fromNameEmail
);
if(!empty($newsletter['sender_address']) &&
!empty($newsletter['sender_name'])
) {
$mailer->fromName = $newsletter['sender_name'];
$mailer->fromEmail = $newsletter['sender_address'];
$mailer->fromNameEmail = sprintf(
'%s <%s>',
$mailer->fromName,
$mailer->fromEmail
);
}
if(!empty($newsletter['reply_to_address']) &&
!empty($newsletter['reply_to_name'])
) {
$mailer->replyToName = $newsletter['reply_to_name'];
$mailer->replyToEmail = $newsletter['reply_to_address'];
$mailer->replyToNameEmail = sprintf(
'%s <%s>',
$mailer->replyToName,
$mailer->replyToEmail
);
}
$result = $mailer->send($data['newsletter'], $subscriber);
$result = $mailer->send($data['newsletter'], $data['subscriber']);
wp_send_json(
array(
'result' => ($result) ? true : false
)
);
}
function buildMailer($mailer = false, $fromName = false, $fromEmail = false, $fromNameEmail = false) {
if(!$mailer) $mailer = $this->mailer;
if(!$fromName) $fromName = $this->fromName;
if(!$fromEmail) $fromEmail = $this->fromEmail;
if(!$fromNameEmail) $fromNameEmail = $this->fromNameEmail;
switch($mailer['method']) {
case 'AmazonSES':
$mailerInstance = new $mailer['class'](
$mailer['region'],
$mailer['access_key'],
$mailer['secret_key'],
$fromNameEmail
);
break;
case 'ElasticEmail':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail, $fromName
);
break;
case 'MailGun':
$mailerInstance = new $mailer['class'](
$mailer['domain'],
$mailer['api_key'],
$fromNameEmail
);
break;
case 'MailPoet':
$mailerInstance = new $mailer['class'](
$mailer['mailpoet_api_key'],
$fromEmail,
$fromName
);
break;
case 'Mandrill':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail, $fromName
);
break;
case 'SendGrid':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail,
$fromName
);
break;
case 'WPMail':
$mailerInstance = new $mailer['class'](
$fromEmail,
$fromName
);
break;
case 'SMTP':
$mailerInstance = new $mailer['class'](
$mailer['host'],
$mailer['port'],
$mailer['authentication'],
$mailer['login'],
$mailer['password'],
$mailer['encryption'],
$fromEmail,
$fromName
);
break;
default:
throw new \Exception('Mailing method does not exist.');
break;
}
return $mailerInstance;
}
function transformSubscriber($subscriber) {
if(!is_array($subscriber)) return $subscriber;
if(isset($subscriber['address'])) $subscriber['email'] = $subscriber['address'];
$first_name = (isset($subscriber['first_name'])) ? $subscriber['first_name'] : '';
$last_name = (isset($subscriber['last_name'])) ? $subscriber['last_name'] : '';
if(!$first_name && !$last_name) return $subscriber['email'];
$subscriber = sprintf('%s %s <%s>', $first_name, $last_name, $subscriber['email']);
$subscriber = trim(preg_replace('!\s\s+!', ' ', $subscriber));
return $subscriber;
}
function getSetting($setting) {
switch($setting) {
case 'mailer':
$mailer = Setting::getValue('mta', null);
if(!$mailer || !isset($mailer['method'])) throw new \Exception('Mailing method is not configured.');
$mailer['class'] = 'MailPoet\\Mailer\\' .
(($this->mailerType[$mailer['method']]) ?
$this->mailerType[$mailer['method']] . '\\' . $mailer['method'] :
$mailer['method']
);
return $mailer;
break;
case 'sender':
$sender = Setting::getValue($setting, null);
if(!$sender) throw new \Exception('Sender name and email are not configured.');
return array(
$sender['name'],
$sender['address'],
sprintf('%s <%s>', $sender['name'], $sender['address'])
);
break;
default:
return Setting::getValue($setting, null);
break;
}
}
}

View File

@ -1,196 +0,0 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Queue {
function controlDaemon($data) {
switch($data['action']) {
case 'start':
$supervisor = new Supervisor($forceStart = true);
wp_send_json(
array(
'result' => $supervisor->checkDaemon() ?
true :
false
)
);
break;
case 'stop':
$status = 'stopped';
break;
default:
$status = 'paused';
break;
}
$daemon = new Daemon();
if(!$daemon->daemon || $daemon->daemonData['status'] !== 'started') {
$result = false;
} else {
$daemon->daemonData['status'] = $status;
$daemon->daemon->value = json_encode($daemon->daemonData);
$result = $daemon->daemon->save();
}
wp_send_json(
array(
'result' => $result
)
);
}
function getDaemonStatus() {
$daemon = new \MailPoet\Cron\BootStrapMenu();
wp_send_json($daemon->bootStrap());
}
function addQueue($data) {
$queue = SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
!d($queue);
exit;
$queue = SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()
->findArray(),
'id'
));
}
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = json_encode(
array(
'to_process' => $subscriber_ids
)
);
$queue->count_total = $queue->count_to_process = count($subscriber_ids);
$queue->save();
wp_send_json(
!$queue->save() ?
array(
'result' => false,
'error' => 'Queue could not be created.'
) :
array(
'result' => true,
'data' => array($queue->id)
)
);
}
function addQueues($data) {
$result = array_map(function ($queueData) {
$queue = SendingQueue::create();
$queue->newsletter_id = $queueData['newsletter_id'];
$queue->subscribers = json_encode(
array(
'to_process' => $queueData['subscribers']
)
);
$queue->count_total = $queue->count_to_process = count($queueData['subscribers']);
$queue->save();
return array(
'newsletter_id' => $queue->newsletter_id,
'queue_id' => $queue->id
);
}, $data);
$result = Helpers::arrayColumn($result, 'queue_id', 'newsletter_id');
wp_send_json(
count($data) != count($result) ?
array(
'result' => false,
'error' => __('Some queues could not be created.'),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'error' => __('Queue not found.')
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'error' => __('Queues not found.')
)
);
}
foreach($queues as $queue) {
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
}
wp_send_json(array('result' => true));
}
function getQueueStatus($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

View File

@ -19,7 +19,6 @@ class SendingQueue {
'errors' => array($e->getMessage())
)
);
exit;
}
$queue = \MailPoet\Models\SendingQueue::where('newsletter_id', $data['newsletter_id'])

View File

@ -0,0 +1,100 @@
<?php
use MailPoet\Mailer\Mailer;
class MailerCest {
function _before() {
$this->sender = array(
'name' => 'Sender',
'address' => 'staff@mailinator.com'
);
$this->replyTo = array(
'name' => 'Reply To',
'address' => 'staff@mailinator.com'
);
$this->mailer = array(
'method' => 'MailPoet',
'mailpoet_api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU'
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
'subject' => 'testing Mailer',
'body' => array(
'html' => 'HTML body',
'text' => 'TEXT body'
)
);
}
function itRequiresMailerMethod() {
try {
$mailer = new Mailer();
} catch (Exception $e) {
expect($e->getMessage())->equals('Mailer is not configured.');
}
}
function itRequiresSender() {
try {
$mailer = new Mailer($mailer = $this->mailer);
} catch (Exception $e) {
expect($e->getMessage())->equals('Sender name and email are not configured.');
}
}
function itCanConstruct() {
$mailer = new Mailer($this->mailer, $this->sender, $this->replyTo);
expect($mailer->sender['fromName'])->equals($this->sender['name']);
expect($mailer->sender['fromEmail'])->equals($this->sender['address']);
expect($mailer->replyTo['replyToName'])->equals($this->replyTo['name']);
expect($mailer->replyTo['replyToEmail'])->equals($this->replyTo['address']);
}
function itCanBuildMailerInstance() {
$mailer = new Mailer($this->mailer, $this->sender);
expect(get_class($mailer->mailerInstance))
->equals('MailPoet\Mailer\Methods\MailPoet');
}
function itCanAbortWhenMethodDoesNotExist() {
try {
$mailer = new Mailer(array('method' => 'test'), $this->sender);
} catch (Exception $e) {
expect($e->getMessage())->equals('Mailing method does not exist.');
}
}
function itCanTransformSubscriber() {
$mailer = new Mailer($this->mailer, $this->sender, $this->replyTo);
expect($mailer->transformSubscriber('test@email.com'))
->equals('test@email.com');
expect($mailer->transformSubscriber(
array(
'email' => 'test@email.com'
))
)->equals('test@email.com');
expect($mailer->transformSubscriber(
array(
'first_name' => 'First',
'email' => 'test@email.com'
))
)->equals('First <test@email.com>');
expect($mailer->transformSubscriber(
array(
'last_name' => 'Last',
'email' => 'test@email.com'
))
)->equals('Last <test@email.com>');
expect($mailer->transformSubscriber(
array(
'first_name' => 'First',
'last_name' => 'Last',
'email' => 'test@email.com'
))
)->equals('First Last <test@email.com>');
}
function itCanSend() {
$mailer = new Mailer($this->mailer, $this->sender, $this->replyTo);
expect($mailer->send($this->newsletter, $this->subscriber))->true();
}
}

View File

@ -1,12 +1,11 @@
<?php
use MailPoet\Mailer\API\AmazonSES;
use MailPoet\Mailer\Methods\AmazonSES;
class AmazonSESCest {
function _before() {
$this->settings = array(
'method' => 'AmazonSES',
'type' => 'API',
'access_key' => 'AKIAJM6Y5HMGXBLDNSRA',
'secret_key' => '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',
'region' => 'us-east-1',

View File

@ -1,12 +1,11 @@
<?php
use MailPoet\Mailer\API\ElasticEmail;
use MailPoet\Mailer\Methods\ElasticEmail;
class ElasticEmailCest {
function _before() {
$this->settings = array(
'method' => 'ElasticEmail',
'type' => 'API',
'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa'
);
$this->fromEmail = 'staff@mailpoet.com';

View File

@ -1,12 +1,11 @@
<?php
use MailPoet\Mailer\API\MailGun;
use MailPoet\Mailer\Methods\MailGun;
class MailGunCest {
function _before() {
$this->settings = array(
'method' => 'MailGun',
'type' => 'API',
'api_key' => 'key-6cf5g5qjzenk-7nodj44gdt8phe6vam2',
'domain' => 'mrcasual.com'
);

View File

@ -1,6 +1,6 @@
<?php
use MailPoet\Mailer\MailPoet;
use MailPoet\Mailer\Methods\MailPoet;
class MailPoetCest {
function _before() {

View File

@ -1,12 +1,11 @@
<?php
use MailPoet\Mailer\API\Mandrill;
use MailPoet\Mailer\Methods\Mandrill;
class MandrillCest {
function _before() {
$this->settings = array(
'method' => 'Mandrill',
'type' => 'API',
'api_key' => '692ys1B7REEoZN7R-dYwNA'
);
$this->fromEmail = 'staff@mailpoet.com';

View File

@ -1,6 +1,6 @@
<?php
use MailPoet\Mailer\SMTP;
use MailPoet\Mailer\Methods\SMTP;
class SMTPCest {
function _before() {

View File

@ -1,12 +1,11 @@
<?php
use MailPoet\Mailer\API\SendGrid;
use MailPoet\Mailer\Methods\SendGrid;
class SendGridCest {
function _before() {
$this->settings = array(
'method' => 'SendGrid',
'type' => 'API',
'api_key' => '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'
);
$this->fromEmail = 'staff@mailpoet.com';

View File

@ -1,6 +1,6 @@
<?php
use MailPoet\Mailer\WPMail;
use MailPoet\Mailer\Methods\WPMail;
class WPMailCest {
function _before() {