- Places supervisor/daemon/worker under the new Cron class

- Updates endpoints
- Integrates queue worker with MailPoet mailer
- Fixes script activation check logic
This commit is contained in:
MrCasual
2015-12-02 22:48:15 -05:00
parent 48fbce22e7
commit d2e5fb89c2
31 changed files with 700 additions and 299 deletions

View File

@ -2,22 +2,20 @@ define(
[ [
'react', 'react',
'react-dom', 'react-dom',
'mailpoet', 'mailpoet'
'classnames'
], ],
function ( function (
React, React,
ReactDOM, ReactDOM,
MailPoet, MailPoet
classNames
) { ) {
var QueueControl = React.createClass({ var CronControl = React.createClass({
getInitialState: function () { getInitialState: function () {
return (queueDaemon) ? queueDaemon : null; return (cronDaemon) ? cronDaemon : null;
}, },
getDaemonData: function () { getDaemonData: function () {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'queue', endpoint: 'cron',
action: 'getDaemonStatus' action: 'getDaemonStatus'
}).done(function (response) { }).done(function (response) {
jQuery('.button-primary').removeClass('disabled'); jQuery('.button-primary').removeClass('disabled');
@ -37,7 +35,7 @@ define(
controlDaemon: function (action) { controlDaemon: function (action) {
jQuery('.button-primary').addClass('disabled'); jQuery('.button-primary').addClass('disabled');
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'queue', endpoint: 'cron',
action: 'controlDaemon', action: 'controlDaemon',
data: {'action': action} data: {'action': action}
}).done(function (response) { }).done(function (response) {
@ -60,7 +58,7 @@ define(
return ( return (
<div> <div>
<div> <div>
Queue daemon is running. Cron daemon is running.
<br/> <br/>
<br/> <br/>
It was started It was started
@ -89,11 +87,11 @@ define(
} }
} }
}); });
let container = document.getElementById('queue_container'); let container = document.getElementById('cron_container');
if (container) { if (container) {
ReactDOM.render( ReactDOM.render(
<QueueControl />, <CronControl />,
document.getElementById('queue_status') document.getElementById('cron_status')
) )
} }
} }

View File

@ -94,7 +94,7 @@ define(
], ],
handleSend: function() { handleSend: function() {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'queue', endpoint: 'sendingQueue',
action: 'addQueue', action: 'addQueue',
data: { data: {
newsletter_id: this.props.params.id, newsletter_id: this.props.params.id,

View File

@ -77,11 +77,16 @@ class Env {
static function isPluginActivated() { static function isPluginActivated() {
$activatesPlugins = get_option('active_plugins'); $activatesPlugins = get_option('active_plugins');
$isActivated = $isActivated = (
in_array( in_array(
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name), sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
$activatesPlugins $activatesPlugins
); ) ||
in_array(
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
$activatesPlugins
)
);
return ($isActivated) ? true : false; return ($isActivated) ? true : false;
} }
} }

View File

@ -2,7 +2,7 @@
namespace MailPoet\Config; namespace MailPoet\Config;
use MailPoet\Models; use MailPoet\Models;
use MailPoet\Queue\Supervisor; use MailPoet\Cron\Supervisor;
use MailPoet\Router; use MailPoet\Router;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -55,7 +55,7 @@ class Initializer {
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field'; $subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields'; $newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option'; $newsletter_option = Env::$db_prefix . 'newsletter_option';
$queues = Env::$db_prefix . 'queues'; $sending_queue = Env::$db_prefix . 'sending_queue';
$newsletter_statistics = Env::$db_prefix . 'newsletter_statistics'; $newsletter_statistics = Env::$db_prefix . 'newsletter_statistics';
define('MP_SUBSCRIBERS_TABLE', $subscribers); define('MP_SUBSCRIBERS_TABLE', $subscribers);
@ -72,7 +72,7 @@ class Initializer {
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field); define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields); define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option); define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_QUEUES_TABLE', $queues); define('MP_SENDING_QUEUE_TABLE', $sending_queue);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics); define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
} }

View File

@ -172,13 +172,13 @@ class Menu {
add_submenu_page( add_submenu_page(
'mailpoet', 'mailpoet',
__('Queue'), __('Cron'),
__('Queue'), __('Cron'),
'manage_options', 'manage_options',
'mailpoet-queue', 'mailpoet-cron',
array( array(
$this, $this,
'queue' 'cron'
) )
); );
} }
@ -374,9 +374,9 @@ class Menu {
echo $this->renderer->render('form/editor.html', $data); echo $this->renderer->render('form/editor.html', $data);
} }
function queue() { function cron() {
$daemon = new \MailPoet\Queue\BootStrapMenu(); $daemon = new \MailPoet\Cron\BootStrapMenu();
$data['daemon'] = json_encode($daemon->bootstrap()); $data['daemon'] = json_encode($daemon->bootstrap());
echo $this->renderer->render('queue.html', $data); echo $this->renderer->render('cron.html', $data);
} }
} }

View File

@ -21,7 +21,7 @@ class Migrator {
'subscriber_custom_field', 'subscriber_custom_field',
'newsletter_option_fields', 'newsletter_option_fields',
'newsletter_option', 'newsletter_option',
'queues', 'sending_queue',
'newsletter_statistics', 'newsletter_statistics',
'forms' 'forms'
); );
@ -205,12 +205,12 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
} }
function queues() { function sending_queue() {
$attributes = array( $attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'subscribers longtext,', 'subscribers longtext,',
'status varchar(12) NOT NULL,', 'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,', 'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,', 'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,', 'count_processed mediumint(9) NOT NULL DEFAULT 0,',

View File

@ -1,7 +1,7 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use MailPoet\Queue\Daemon; use MailPoet\Cron\Daemon;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;

View File

@ -1,18 +1,16 @@
<?php <?php
namespace MailPoet\Queue; namespace MailPoet\Cron;
use Carbon\Carbon; use Carbon\Carbon;
use MailPoet\Models\Queue;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
class BootStrapMenu { class BootStrapMenu {
function __construct() { function __construct() {
$this->daemon = Setting::where('name', 'daemon') $this->daemon = Setting::where('name', 'cron_daemon')
->findOne(); ->findOne();
} }
function bootStrap() { function bootStrap() {
$queues = Queue::findMany();
return ($this->daemon) ? return ($this->daemon) ?
array_merge( array_merge(
array( array(
@ -21,13 +19,15 @@ class BootStrapMenu {
'Y-m-d H:i:s', 'Y-m-d H:i:s',
$this->daemon->created_at, $this->daemon->created_at,
'UTC' 'UTC'
)->diffForHumans(), )
->diffForHumans(),
'timeSinceUpdate' => 'timeSinceUpdate' =>
Carbon::createFromFormat( Carbon::createFromFormat(
'Y-m-d H:i:s', 'Y-m-d H:i:s',
$this->daemon->updated_at, $this->daemon->updated_at,
'UTC' 'UTC'
)->diffForHumans() )
->diffForHumans()
), ),
json_decode($this->daemon->value, true) json_decode($this->daemon->value, true)
) : ) :

View File

@ -1,5 +1,5 @@
<?php <?php
namespace MailPoet\Queue; namespace MailPoet\Cron;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Util\Security; use MailPoet\Util\Security;
@ -27,29 +27,25 @@ class Daemon {
$daemonData = $this->daemonData; $daemonData = $this->daemonData;
if(!$daemon) { if(!$daemon) {
$daemon = Setting::create(); $daemon = Setting::create();
$daemon->name = 'daemon'; $daemon->name = 'cron_daemon';
$daemon->value = json_encode( $daemonData = array(
array( 'status' => null,
'status' => 'stopped', 'counter' => 0
)); );
$daemon->value = json_encode($daemonData);
$daemon->save(); $daemon->save();
} }
if($daemonData['status'] !== 'started') { if($daemonData['status'] !== 'started') {
$_SESSION['daemon'] = 'started'; $_SESSION['cron_daemon'] = 'started';
$daemonData = array( $daemonData['status'] = 'started';
'status' => 'started', $daemonData['token'] = $this->refreshedToken;
'token' => $this->refreshedToken, $_SESSION['cron_daemon'] = array('result' => true);
'counter' => $daemonData['status'] === 'paused' ?
$daemonData['counter'] :
0
);
$_SESSION['daemon'] = array('result' => true);
$this->manageSession('end'); $this->manageSession('end');
$daemon->value = json_encode($daemonData); $daemon->value = json_encode($daemonData);
$daemon->save(); $daemon->save();
$this->callSelf(); $this->callSelf();
} else { } else {
$_SESSION['daemon'] = array( $_SESSION['cron_daemon'] = array(
'result' => false, 'result' => false,
'error' => 'already started' 'error' => 'already started'
); );
@ -67,7 +63,7 @@ class Daemon {
$this->abortWithError('invalid token'); $this->abortWithError('invalid token');
} }
$worker = new Worker(); $worker = new Worker($this->timer);
$worker->process(); $worker->process();
$elapsedTime = microtime(true) - $this->timer; $elapsedTime = microtime(true) - $this->timer;
if($elapsedTime < 30) { if($elapsedTime < 30) {
@ -84,7 +80,7 @@ class Daemon {
} }
function getDaemon() { function getDaemon() {
$daemon = Setting::where('name', 'daemon') $daemon = Setting::where('name', 'cron_daemon')
->findOne(); ->findOne();
return array( return array(
($daemon) ? $daemon : null, ($daemon) ? $daemon : null,

View File

@ -1,5 +1,5 @@
<?php <?php
namespace MailPoet\Queue; namespace MailPoet\Cron;
use Carbon\Carbon; use Carbon\Carbon;
use MailPoet\Config\Env; use MailPoet\Config\Env;
@ -19,51 +19,41 @@ class Supervisor {
function checkDaemon() { function checkDaemon() {
if(!$this->daemon) { if(!$this->daemon) {
return $this->startDaemon(); return $this->startDaemon();
} else {
if(!$this->forceStart && (
$this->daemonData['status'] === 'paused' ||
$this->daemonData['status'] === 'stopped'
)
) {
return;
}
$currentTime = Carbon::now('UTC');
$lastUpdateTime = Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at, 'UTC'
);
$timeSinceLastStart = $currentTime->diffInSeconds($lastUpdateTime);
if(!$this->forceStart && $timeSinceLastStart < 50) return;
if(
($this->forceStart && $this->daemonData['status'] === 'paused') ||
!$this->forceStart
) {
$this->daemonData['status'] = 'paused';
}
$this->daemon->value = json_encode($this->daemonData);
$this->daemon->save();
return $this->startDaemon();
} }
if(!$this->forceStart && $this->daemonData['status'] === 'stopped') {
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;
$this->daemon->value = json_encode($this->daemonData);
$this->daemon->save();
return $this->startDaemon();
} }
function startDaemon() { function startDaemon() {
if(!session_id()) session_start(); if(!session_id()) session_start();
$sessionId = session_id(); $sessionId = session_id();
session_write_close(); session_write_close();
$_SESSION['daemon'] = null; $_SESSION['cron_daemon'] = null;
$payload = json_encode(array('session' => $sessionId)); $payload = json_encode(array('session' => $sessionId));
self::getRemoteUrl( self::getRemoteUrl(
'/?mailpoet-api&section=queue&action=start&payload=' . urlencode($payload) '/?mailpoet-api&section=queue&action=start&payload=' . urlencode($payload)
); );
session_start(); session_start();
$daemonStatus = $_SESSION['daemon']; $daemonStatus = $_SESSION['cron_daemon'];
unset($_SESSION['daemon']); unset($_SESSION['daemon']);
session_write_close(); session_write_close();
return $daemonStatus; return $daemonStatus;
} }
function getDaemon() { function getDaemon() {
$daemon = Setting::where('name', 'daemon') $daemon = Setting::where('name', 'cron_daemon')
->findOne(); ->findOne();
$daemonData = ($daemon) ? json_decode($daemon->value, true) : false; $daemonData = ($daemon) ? json_decode($daemon->value, true) : false;
return array( return array(

102
lib/Cron/Worker.php Normal file
View File

@ -0,0 +1,102 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Router\Mailer;
if(!defined('ABSPATH')) exit;
class Worker {
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
}
function process() {
$queues =
SendingQueue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
// TODO: usee Mailer's method to get the mailer from DB
$mailer = new Mailer();
$mailer->mailer['method'] = 'MailPoet';
$mailer->mailer['class'] = 'MailPoet\\Mailer\\MailPoet';
$mailer->mailer['api_key'] = Setting::getValue('api_key', 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU');
foreach($queues as $queue) {
if($this->checkExecutionTimer()) break;
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) {
continue;
};
$newsletter = $newsletter->asArray();
// TODO: render newsletter
$newsletter = array(
'subject' => $newsletter['subject'],
'id' => $newsletter['id'],
'body' => array(
'html' => 'rendering not yet implemented',
'text' => 'rendering not yet implemented'
)
);
$subscribers = json_decode($queue->subscribers, true);
$subscribersToProcess = $subscribers['to_process'];
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
foreach(array_chunk($subscribersToProcess, 200) as $subscriberIds) {
if($this->checkExecutionTimer()) break;
$dbSubscribers = Subscriber::whereIn('id', $subscriberIds)
->findArray();
foreach($dbSubscribers as $i => $dbSubscriber) {
if($this->checkExecutionTimer()) break;
// TODO: replace shortcodes in the newsletter
$result = $mailer->send(
$newsletter,
sprintf(
"%s %s <%s>",
$dbSubscriber['first_name'],
$dbSubscriber['last_name'],
$dbSubscriber['email']
)
);
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $dbSubscriber['id'];
$newsletterStatistics->newsletter_id = $newsletter['id'];
$newsletterStatistics->queue_id = $queue->id;
$newsletterStatistics->save();
if($result) {
$subscribers['processed'][] = $dbSubscriber['id'];
} else {
$subscribers['failed'][] = $dbSubscriber['id'];
}
$subscribers['to_process'] = array_values(
array_diff(
$subscribers['to_process'],
array_merge($subscribers['processed'], $subscribers['failed'])
)
);
$queue->count_processed =
count($subscribers['processed']) + count($subscribers['failed']);
$queue->count_to_process = count($subscribers['to_process']);
$queue->count_failed = count($subscribers['failed']);
$queue->count_total =
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = date('Y-m-d H:i:s');
$queue->status = 'completed';
}
$queue->subscribers = json_encode($subscribers);
$queue->save();
}
}
}
}
function checkExecutionTimer() {
$elapsedTime = microtime(true) - $this->timer;
return ($elapsedTime >= 28) ? true : false;
}
}

View File

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

View File

@ -1,66 +0,0 @@
<?php
namespace MailPoet\Queue;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Queue;
if(!defined('ABSPATH')) exit;
class Worker {
function __construct($timer = false) {
$this->timer = $timer;
$this->timer = microtime(true);
}
function process() {
$queues =
Queue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNotIn('status', array(
'paused',
'completed'
))
->findResultSet();
foreach($queues as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) {
continue;
};
$newsletter = $newsletter->asArray();
$subscribers = json_decode($queue->subscribers, true);
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
$subscribersToProcess = $subscribers['to_process'];
foreach($subscribersToProcess as $subscriber) {
$elapsedTime = microtime(true) - $this->timer;
if($elapsedTime >= 28) break;
// TODO: hook up to mailer
sleep(1);
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $subscriber;
$newsletterStatistics->newsletter_id = $newsletter['id'];
$newsletterStatistics->queue_id = $queue->id;
$newsletterStatistics->save();
$subscribers['processed'][] = $subscriber;
$subscribers['to_process'] = array_values(
array_diff(
$subscribers['to_process'],
$subscribers['processed']
)
);
$queue->count_processed = count($subscribers['processed']);
$queue->count_to_process = count($subscribers['to_process']);
$queue->count_failed = count($subscribers['failed']);
$queue->count_total =
$queue->count_processed + $queue->count_to_process + $queue->count_failed;
if(!$queue->count_to_process) {
$queue->processed_at = date('Y-m-d H:i:s');
$queue->status = 'completed';
}
$queue->subscribers = json_encode($subscribers);
$queue->save();
}
}
}
}

194
lib/Router/Cron.php Normal file
View File

@ -0,0 +1,194 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Cron {
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

@ -1,12 +1,15 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use MailPoet\Models\Setting;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Mailer { class Mailer {
function __construct() { function __construct() {
$this->fromName = $this->getSetting('from_name'); list($this->fromName, $this->fromEmail) = $this->getSetting('sender');
$this->fromEmail = $this->getSetting('from_address');
$this->mailer = $this->getSetting('mailer'); $this->mailer = $this->getSetting('mailer');
$this->from = sprintf('%s <%s>', $this->fromName, $this->fromEmail); $this->from = sprintf('%s <%s>', $this->fromName, $this->fromEmail);
} }
@ -14,61 +17,61 @@ class Mailer {
function send($newsletter, $subscriber) { function send($newsletter, $subscriber) {
$subscriber = $this->transformSubscriber($subscriber); $subscriber = $this->transformSubscriber($subscriber);
$mailer = $this->buildMailer(); $mailer = $this->buildMailer();
return wp_send_json($mailer->send($newsletter, $subscriber)); return $mailer->send($newsletter, $subscriber);
} }
function buildMailer() { function buildMailer() {
switch ($this->mailer['name']) { switch($this->mailer['method']) {
case 'AmazonSES': case 'AmazonSES':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['region'], $this->mailer['region'],
$this->mailer['access_key'], $this->mailer['access_key'],
$this->mailer['secret_key'], $this->mailer['secret_key'],
$this->from $this->from
); );
break; break;
case 'ElasticEmail': case 'ElasticEmail':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['api_key'], $this->mailer['api_key'],
$this->fromEmail, $this->fromName $this->fromEmail, $this->fromName
); );
break; break;
case 'MailGun': case 'MailGun':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['domain'], $this->mailer['domain'],
$this->mailer['api_key'], $this->mailer['api_key'],
$this->from $this->from
); );
break; break;
case 'MailPoet': case 'MailPoet':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['api_key'], $this->mailer['api_key'],
$this->fromEmail, $this->fromEmail,
$this->fromName $this->fromName
); );
break; break;
case 'Mandrill': case 'Mandrill':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['api_key'], $this->mailer['api_key'],
$this->fromEmail, $this->fromName $this->fromEmail, $this->fromName
); );
break; break;
case 'SendGrid': case 'SendGrid':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['api_key'], $this->mailer['api_key'],
$this->fromEmail, $this->fromEmail,
$this->fromName $this->fromName
); );
break; break;
case 'SMTP': case 'SMTP':
$mailer = new $this->mailer['class']( $mailer = new $this->mailer['class'](
$this->mailer['host'], $this->mailer['host'],
$this->mailer['port'], $this->mailer['port'],
$this->mailer['authentication'], $this->mailer['authentication'],
$this->mailer['encryption'], $this->mailer['encryption'],
$this->fromEmail, $this->fromEmail,
$this->fromName); $this->fromName);
break; break;
} }
return $mailer; return $mailer;
} }
@ -84,65 +87,72 @@ class Mailer {
} }
function getSetting($setting) { function getSetting($setting) {
if($setting === 'mailer') { switch($setting) {
$mailers = array( case 'mailer':
array( // TODO: remove
'name' => 'AmazonSES', /* $mailers = array(
'type' => 'API', array(
'access_key' => 'AKIAJM6Y5HMGXBLDNSRA', 'method' => 'AmazonSES',
'secret_key' => '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', 'type' => 'API',
'region' => 'us-east-1' 'access_key' => 'AKIAJM6Y5HMGXBLDNSRA',
), 'secret_key' => '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',
array( 'region' => 'us-east-1'
'name' => 'ElasticEmail', ),
'type' => 'API', array(
'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa' 'method' => 'ElasticEmail',
), 'type' => 'API',
array( 'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa'
'name' => 'MailGun', ),
'type' => 'API', array(
'api_key' => 'key-6cf5g5qjzenk-7nodj44gdt8phe6vam2', 'method' => 'MailGun',
'domain' => 'mrcasual.com' 'type' => 'API',
), 'api_key' => 'key-6cf5g5qjzenk-7nodj44gdt8phe6vam2',
array( 'domain' => 'mrcasual.com'
'name' => 'MailPoet', ),
'api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU' array(
), 'method' => 'MailPoet',
array( 'api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU'
'name' => 'Mandrill', ),
'type' => 'API', array(
'api_key' => '692ys1B7REEoZN7R-dYwNA' 'method' => 'Mandrill',
), 'type' => 'API',
array( 'api_key' => '692ys1B7REEoZN7R-dYwNA'
'name' => 'SendGrid', ),
'type' => 'API', array(
'api_key' => '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' 'method' => 'SendGrid',
), 'type' => 'API',
array( 'api_key' => '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'
'name' => 'SMTP', ),
'host' => 'email-smtp.us-west-2.amazonaws.com', array(
'port' => 587, 'method' => 'SMTP',
'authentication' => array( 'host' => 'email-smtp.us-west-2.amazonaws.com',
'login' => 'AKIAIGPBLH6JWG5VCBQQ', 'port' => 587,
'password' => 'AudVHXHaYkvr54veCzqiqOxDiMMyfQW3/V6F1tYzGXY3' 'authentication' => array(
), 'login' => 'AKIAIGPBLH6JWG5VCBQQ',
'encryption' => 'tls' 'password' => 'AudVHXHaYkvr54veCzqiqOxDiMMyfQW3/V6F1tYzGXY3'
), ),
array( 'encryption' => 'tls'
'name' => 'WPMail' ),
) array(
); 'method' => 'WPMail'
$mailer = $mailers[array_rand($mailers)]; )
$mailer['class'] = 'MailPoet\\Mailer\\' . );*/
((isset($mailer['type'])) ? $mailer = Setting::getValue('mta', null);
$mailer['type'] . '\\' . $mailer['name'] : if(!$mailer) throw new \Exception('Mailing method has not been configured.');
$mailer['name'] $mailer['class'] = 'MailPoet\\Mailer\\' .
); ((isset($mailer['type'])) ?
return $mailer; $mailer['type'] . '\\' . $mailer['method'] :
$mailer['method']
);
return $mailer;
break;;
case 'sender':
$sender = Setting::getValue($setting, null);
return array($sender['name'], $sender['address']);
break;
default:
return Setting::getValue($setting, null);
break;
} }
if($setting === 'from_name') return 'Sender';
if($setting === 'from_address') return 'staff@mailpoet.com';
return Setting::where('name', $setting)
->findOne()->value;
} }
} }

View File

@ -12,7 +12,7 @@ use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterOptionField; use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption; use MailPoet\Models\NewsletterOption;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Models\Queue as SendingQueue; use MailPoet\Models\SendingQueue;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -231,6 +231,7 @@ class Newsletters {
// get queue // get queue
$queue = SendingQueue::where('newsletter_id', $item['id']) $queue = SendingQueue::where('newsletter_id', $item['id'])
->orderByDesc('updated_at')
->findOne(); ->findOne();
$item['queue'] = ($queue !== false) ? $queue->asArray() : null; $item['queue'] = ($queue !== false) ? $queue->asArray() : null;
} }

View File

@ -1,10 +1,8 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use MailPoet\Queue\Daemon;
use MailPoet\Queue\Supervisor;
use MailPoet\Models\Queue as SendingQueue;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -45,20 +43,29 @@ class Queue {
} }
function getDaemonStatus() { function getDaemonStatus() {
$daemon = new \MailPoet\Queue\BootStrapMenu(); $daemon = new \MailPoet\Cron\BootStrapMenu();
wp_send_json($daemon->bootStrap()); wp_send_json($daemon->bootStrap());
} }
function addQueue($data) { function addQueue($data) {
$queue = SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
!d($queue);
exit;
$queue = SendingQueue::create(); $queue = SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id']; $queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array(); $subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])->findMany(); $segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) { foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn( $subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()->findArray(), $segment->subscribers()
->findArray(),
'id' 'id'
)); ));
} }

167
lib/Router/SendingQueue.php Normal file
View File

@ -0,0 +1,167 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class SendingQueue {
function addQueue($data) {
$queue = \MailPoet\Models\SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
if(count($queue)) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Send operation is already in progress.'))
)
);
}
$queue = \MailPoet\Models\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,
'errors' => array(__('Queue could not be created.'))
) :
array(
'result' => true,
'data' => array($queue->id)
)
);
}
function addQueues($data) {
$newsletterIds = Helpers::arrayColumn($data, 'newsletter_id');
$queues = \MailPoet\Models\SendingQueue::whereIn('newsletter_id', $newsletterIds)
->whereNull('status')
->findArray();
if(count($queues)) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Send operation is already in progress.'))
)
);
}
$result = array_map(function ($queueData) {
$queue = \MailPoet\Models\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,
'errors' => array(__('Some queues could not be created.')),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Queue not found.'))
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('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 = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'errors' => array(__('Queue not found.'))
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'errors' => array(__('Queue not found.'))
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

View File

@ -63,7 +63,7 @@ class Import {
); );
} }
} }
} catch (\PDOException $e) { } catch(\PDOException $e) {
return array( return array(
'result' => false, 'result' => false,
'error' => $e->getMessage() 'error' => $e->getMessage()
@ -145,7 +145,7 @@ class Import {
); );
if(!$existingTrashedRecords) return; if(!$existingTrashedRecords) return;
$existingTrashedRecords = Helpers::flattenArray($existingTrashedRecords); $existingTrashedRecords = Helpers::flattenArray($existingTrashedRecords);
foreach (array_chunk($existingTrashedRecords, 200) as $subscriberIds) { foreach(array_chunk($existingTrashedRecords, 200) as $subscriberIds) {
Subscriber::whereIn('id', $subscriberIds) Subscriber::whereIn('id', $subscriberIds)
->deleteMany(); ->deleteMany();
SubscriberSegment::whereIn('subscriber_id', $subscriberIds) SubscriberSegment::whereIn('subscriber_id', $subscriberIds)
@ -247,7 +247,7 @@ class Import {
}, $subscriberFields); }, $subscriberFields);
}, range(0, $subscribersCount)); }, range(0, $subscribersCount));
$currentTime = ($action === 'update') ? date('Y-m-d H:i:s') : $this->currentTime; $currentTime = ($action === 'update') ? date('Y-m-d H:i:s') : $this->currentTime;
foreach (array_chunk($subscribers, 200) as $data) { foreach(array_chunk($subscribers, 100) as $data) {
if($action == 'create') { if($action == 'create') {
Subscriber::createMultiple( Subscriber::createMultiple(
$subscriberFields, $subscriberFields,
@ -310,7 +310,7 @@ class Import {
); );
}, $count, $subscribersData[$column]); }, $count, $subscribersData[$column]);
}, $subscriberCustomFields)[0]; }, $subscriberCustomFields)[0];
foreach (array_chunk($subscribers, 200) as $data) { foreach(array_chunk($subscribers, 200) as $data) {
if($action === 'create') { if($action === 'create') {
SubscriberCustomField::createMultiple( SubscriberCustomField::createMultiple(
$data $data
@ -325,7 +325,7 @@ class Import {
} }
function addSubscribersToSegments($subscribers, $segments) { function addSubscribersToSegments($subscribers, $segments) {
foreach (array_chunk($subscribers, 200) as $data) { foreach(array_chunk($subscribers, 200) as $data) {
SubscriberSegment::createMultiple($segments, $data); SubscriberSegment::createMultiple($segments, $data);
} }
} }

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\API\AmazonSES;
class AmazonSESCest { class AmazonSESCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'AmazonSES', 'method' => 'AmazonSES',
'type' => 'API', 'type' => 'API',
'access_key' => 'AKIAJM6Y5HMGXBLDNSRA', 'access_key' => 'AKIAJM6Y5HMGXBLDNSRA',
'secret_key' => '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', 'secret_key' => 'P3EbTbVx7U0LXKQ9nTm2eIrP+9aPiLyvaRDsFxXh',

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\API\ElasticEmail;
class ElasticEmailCest { class ElasticEmailCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'ElasticEmail', 'method' => 'ElasticEmail',
'type' => 'API', 'type' => 'API',
'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa' 'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa'
); );

View File

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

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\API\Mandrill;
class MandrillCest { class MandrillCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'Mandrill', 'method' => 'Mandrill',
'type' => 'API', 'type' => 'API',
'api_key' => '692ys1B7REEoZN7R-dYwNA' 'api_key' => '692ys1B7REEoZN7R-dYwNA'
); );

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\API\SendGrid;
class SendGridCest { class SendGridCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'SendGrid', 'method' => 'SendGrid',
'type' => 'API', 'type' => 'API',
'api_key' => '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' 'api_key' => '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'
); );

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\MailPoet;
class MailPoetCest { class MailPoetCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'MailPoet', 'method' => 'MailPoet',
'api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU' 'api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU'
); );
$this->fromEmail = 'staff@mailpoet.com'; $this->fromEmail = 'staff@mailpoet.com';

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\SMTP;
class SMTPCest { class SMTPCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'SMTP', 'method' => 'SMTP',
'host' => 'email-smtp.us-west-2.amazonaws.com', 'host' => 'email-smtp.us-west-2.amazonaws.com',
'port' => 587, 'port' => 587,
'authentication' => array( 'authentication' => array(

View File

@ -5,7 +5,7 @@ use MailPoet\Mailer\WPMail;
class WPMailCest { class WPMailCest {
function _before() { function _before() {
$this->settings = array( $this->settings = array(
'name' => 'WPMail' 'method' => 'WPMail'
); );
$this->fromEmail = 'staff@mailpoet.com'; $this->fromEmail = 'staff@mailpoet.com';
$this->fromName = 'Sender'; $this->fromName = 'Sender';

View File

@ -43,8 +43,8 @@ class MailerCest {
$mailer = $this->router->buildMailer(); $mailer = $this->router->buildMailer();
$class = 'Mailpoet\\Mailer\\' . $class = 'Mailpoet\\Mailer\\' .
((isset($this->router->mailer['type'])) ? ((isset($this->router->mailer['type'])) ?
$this->router->mailer['type'] . '\\' . $this->router->mailer['name'] : $this->router->mailer['type'] . '\\' . $this->router->mailer['method'] :
$this->router->mailer['name'] $this->router->mailer['method']
); );
expect($mailer instanceof $class)->true(); expect($mailer instanceof $class)->true();
expect(method_exists($mailer, 'send'))->true(); expect(method_exists($mailer, 'send'))->true();
@ -52,7 +52,7 @@ class MailerCest {
function itCanSend() { function itCanSend() {
$newsletter = array( $newsletter = array(
'subject' => 'testing Mailer router with ' . $this->router->mailer['name'], 'subject' => 'testing Mailer router with ' . $this->router->mailer['method'],
'body' => array( 'body' => array(
'html' => 'HTML body', 'html' => 'HTML body',
'text' => 'TEXT body' 'text' => 'TEXT body'

10
views/cron.html Normal file
View File

@ -0,0 +1,10 @@
<% extends 'layout.html' %>
<% block content %>
<div id="cron_container">
<div id="cron_status"></div>
</div>
<script>
var cronDaemon = <%= daemon|raw %>
</script>
<% endblock %>

View File

@ -1,13 +0,0 @@
<% extends 'layout.html' %>
<% block content %>
<div id="queue_container">
<div id="queue_status"></div>
<div id="queue_control"></div>
</div>
<script type="text/javascript">
</script>
<script>
var queueDaemon = <%= daemon|raw %>
</script>
<% endblock %>

View File

@ -103,7 +103,7 @@ config.push(_.extend({}, baseConfig, {
'subscribers/importExport/import.js', 'subscribers/importExport/import.js',
'subscribers/importExport/export.js', 'subscribers/importExport/export.js',
'helpscout', 'helpscout',
'queue.jsx' 'cron.jsx'
], ],
form_editor: [ form_editor: [
'form_editor/form_editor.js', 'form_editor/form_editor.js',