Create a newsletter and send it
[MAILPOET-1859]
This commit is contained in:
@@ -3,6 +3,7 @@ namespace MailPoet\Cron\Triggers;
|
|||||||
|
|
||||||
use MailPoet\Cron\Workers\AuthorizedSendingEmailsCheck;
|
use MailPoet\Cron\Workers\AuthorizedSendingEmailsCheck;
|
||||||
use MailPoet\Cron\Workers\InactiveSubscribers;
|
use MailPoet\Cron\Workers\InactiveSubscribers;
|
||||||
|
use MailPoet\Cron\Workers\StatsNotifications\AutomatedEmails;
|
||||||
use MailPoet\Cron\Workers\WooCommerceOrders;
|
use MailPoet\Cron\Workers\WooCommerceOrders;
|
||||||
use MailPoet\Services\Bridge;
|
use MailPoet\Services\Bridge;
|
||||||
use MailPoet\Cron\CronHelper;
|
use MailPoet\Cron\CronHelper;
|
||||||
@@ -101,6 +102,12 @@ class WordPress {
|
|||||||
'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
|
'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
|
||||||
'status' => ['null', ScheduledTask::STATUS_SCHEDULED],
|
'status' => ['null', ScheduledTask::STATUS_SCHEDULED],
|
||||||
]);
|
]);
|
||||||
|
// stats notifications for auto emails
|
||||||
|
$auto_stats_notifications_tasks = self::getTasksCount([
|
||||||
|
'type' => AutomatedEmails::TASK_TYPE,
|
||||||
|
'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
|
||||||
|
'status' => ['null', ScheduledTask::STATUS_SCHEDULED],
|
||||||
|
]);
|
||||||
// inactive subscribers check
|
// inactive subscribers check
|
||||||
$inactive_subscribers_tasks = self::getTasksCount([
|
$inactive_subscribers_tasks = self::getTasksCount([
|
||||||
'type' => InactiveSubscribers::TASK_TYPE,
|
'type' => InactiveSubscribers::TASK_TYPE,
|
||||||
@@ -154,6 +161,7 @@ class WordPress {
|
|||||||
|| $sending_service_key_check_active
|
|| $sending_service_key_check_active
|
||||||
|| $premium_key_check_active
|
|| $premium_key_check_active
|
||||||
|| $stats_notifications_tasks
|
|| $stats_notifications_tasks
|
||||||
|
|| $auto_stats_notifications_tasks
|
||||||
|| $inactive_subscribers_tasks
|
|| $inactive_subscribers_tasks
|
||||||
|| $woo_commerce_sync_tasks
|
|| $woo_commerce_sync_tasks
|
||||||
|| $authorized_email_addresses_tasks
|
|| $authorized_email_addresses_tasks
|
||||||
|
@@ -3,9 +3,14 @@
|
|||||||
namespace MailPoet\Cron\Workers\StatsNotifications;
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use MailPoet\Config\Renderer;
|
||||||
use MailPoet\Cron\Workers\SimpleWorker;
|
use MailPoet\Cron\Workers\SimpleWorker;
|
||||||
|
use MailPoet\Features\FeaturesController;
|
||||||
|
use MailPoet\Mailer\Mailer;
|
||||||
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\ScheduledTask;
|
use MailPoet\Models\ScheduledTask;
|
||||||
use MailPoet\Settings\SettingsController;
|
use MailPoet\Settings\SettingsController;
|
||||||
|
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||||
use MailPoet\WP\Functions as WPFunctions;
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
|
||||||
if (!defined('ABSPATH')) exit;
|
if (!defined('ABSPATH')) exit;
|
||||||
@@ -13,12 +18,34 @@ if (!defined('ABSPATH')) exit;
|
|||||||
class AutomatedEmails extends SimpleWorker {
|
class AutomatedEmails extends SimpleWorker {
|
||||||
const TASK_TYPE = 'stats_notification_automated_emails';
|
const TASK_TYPE = 'stats_notification_automated_emails';
|
||||||
|
|
||||||
|
/** @var \MailPoet\Mailer\Mailer */
|
||||||
|
private $mailer;
|
||||||
|
|
||||||
/** @var SettingsController */
|
/** @var SettingsController */
|
||||||
private $settings;
|
private $settings;
|
||||||
|
|
||||||
function __construct(SettingsController $settings, $timer = false) {
|
/** @var Renderer */
|
||||||
|
private $renderer;
|
||||||
|
|
||||||
|
/** @var WCHelper */
|
||||||
|
private $woocommerce_helper;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $timer;
|
||||||
|
|
||||||
|
function __construct(
|
||||||
|
Mailer $mailer,
|
||||||
|
Renderer $renderer,
|
||||||
|
SettingsController $settings,
|
||||||
|
WCHelper $woocommerce_helper,
|
||||||
|
$timer = false
|
||||||
|
) {
|
||||||
parent::__construct($timer);
|
parent::__construct($timer);
|
||||||
|
$this->mailer = $mailer;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
|
$this->renderer = $renderer;
|
||||||
|
$this->woocommerce_helper = $woocommerce_helper;
|
||||||
|
$this->timer = $timer ?: microtime(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkProcessingRequirements() {
|
function checkProcessingRequirements() {
|
||||||
@@ -42,14 +69,85 @@ class AutomatedEmails extends SimpleWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processTaskStrategy(ScheduledTask $task) {
|
function processTaskStrategy(ScheduledTask $task) {
|
||||||
// TODO
|
try {
|
||||||
// TODO refactor \MailPoet\Cron\Workers\StatsNotifications\Worker and Scheduler and share the code
|
$settings = $this->settings->get(Worker::SETTINGS_KEY);
|
||||||
|
$newsletters = $this->getNewsletters();
|
||||||
|
if ($newsletters) {
|
||||||
|
$this->mailer->send($this->constructNewsletter($newsletters), $settings['address']);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (WP_DEBUG) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
// TODO settings isn't saved properly :(
|
// TODO settings isn't saved properly :(
|
||||||
|
// TODO templates are not ready yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Newsletter[] $newsletters
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function constructNewsletter($newsletters) {
|
||||||
|
$context = $this->prepareContext($newsletters);
|
||||||
|
return [
|
||||||
|
'subject' => __('Your monthly stats are in!', 'mailpoet'),
|
||||||
|
'body' => [
|
||||||
|
'html' => $this->renderer->render('emails/statsNotificationAutomatedEmails.html', $context),
|
||||||
|
'text' => $this->renderer->render('emails/statsNotificationAutomatedEmails.txt', $context),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getNewsletters() {
|
||||||
|
$newsletters = Newsletter
|
||||||
|
::whereNull('deleted_at')
|
||||||
|
->whereIn('type', [Newsletter::TYPE_AUTOMATIC, Newsletter::TYPE_WELCOME])
|
||||||
|
->where('status', Newsletter::STATUS_ACTIVE)
|
||||||
|
->orderByAsc('subject')
|
||||||
|
->findMany();
|
||||||
|
foreach ($newsletters as $newsletter) {
|
||||||
|
$newsletter
|
||||||
|
->withSendingQueue()
|
||||||
|
->withTotalSent()
|
||||||
|
->withStatistics($this->woocommerce_helper, new FeaturesController()); // TODO this is here temporarily until https://github.com/mailpoet/mailpoet/pull/2157 is merged after that we can remove the second argument
|
||||||
|
}
|
||||||
|
$result = [];
|
||||||
|
foreach ($newsletters as $newsletter) {
|
||||||
|
if ($newsletter->total_sent) {
|
||||||
|
$result[] = $newsletter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Newsletter[] $newsletters
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function prepareContext(array $newsletters) {
|
||||||
|
$context = [
|
||||||
|
'linkSettings' => WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-settings#basics'),
|
||||||
|
'newsletters' => [],
|
||||||
|
];
|
||||||
|
foreach ($newsletters as $newsletter) {
|
||||||
|
$clicked = ($newsletter->statistics['clicked'] * 100) / $newsletter->total_sent;
|
||||||
|
$opened = ($newsletter->statistics['opened'] * 100) / $newsletter->total_sent;
|
||||||
|
$context['newsletters'][] = [
|
||||||
|
'linkStats' => WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-newsletters#/stats/' . $newsletter->id),
|
||||||
|
'clicked' => $clicked,
|
||||||
|
'opened' => $opened,
|
||||||
|
'subject' => $newsletter->subject,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function getNextRunDate() {
|
static function getNextRunDate() {
|
||||||
$wp = new WPFunctions;
|
$wp = new WPFunctions;
|
||||||
$date = Carbon::createFromTimestamp($wp->currentTime('timestamp'));
|
$date = Carbon::createFromTimestamp($wp->currentTime('timestamp'));
|
||||||
return $date->next(Carbon::MONDAY)->midDay();
|
return $date->endOfMonth()->next(Carbon::MONDAY)->midDay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -110,7 +110,7 @@ class WorkersFactory {
|
|||||||
|
|
||||||
/** @return StatsNotificationsWorkerForAutomatedEmails */
|
/** @return StatsNotificationsWorkerForAutomatedEmails */
|
||||||
function createStatsNotificationsWorkerForAutomatedEmails($timer) {
|
function createStatsNotificationsWorkerForAutomatedEmails($timer) {
|
||||||
return new StatsNotificationsWorkerForAutomatedEmails($this->settings, $timer);
|
return new StatsNotificationsWorkerForAutomatedEmails($this->mailer, $this->renderer, $this->settings, $this->woocommerce_helper, $timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return SendingServiceKeyCheckWorker */
|
/** @return SendingServiceKeyCheckWorker */
|
||||||
|
@@ -70,7 +70,7 @@ if (!defined('ABSPATH')) exit;
|
|||||||
* @method $this whereNotIn($column_name, $values)
|
* @method $this whereNotIn($column_name, $values)
|
||||||
* @method static $this whereNotIn($column_name, $values)
|
* @method static $this whereNotIn($column_name, $values)
|
||||||
* @method $this whereNull($column_name)
|
* @method $this whereNull($column_name)
|
||||||
* @method static $this whereNull($column_name)
|
* @method static static whereNull($column_name)
|
||||||
* @method $this whereNotNull($column_name)
|
* @method $this whereNotNull($column_name)
|
||||||
* @method static $this whereNotNull($column_name)
|
* @method static $this whereNotNull($column_name)
|
||||||
* @method $this whereRaw($clause, $parameters=array())
|
* @method $this whereRaw($clause, $parameters=array())
|
||||||
|
@@ -0,0 +1,218 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
|
use MailPoet\Config\Renderer;
|
||||||
|
use MailPoet\Mailer\Mailer;
|
||||||
|
use MailPoet\Models\Newsletter;
|
||||||
|
use MailPoet\Models\ScheduledTask;
|
||||||
|
use MailPoet\Settings\SettingsController;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||||
|
|
||||||
|
class AutomatedEmailsTest extends \MailPoetTest {
|
||||||
|
|
||||||
|
/** @var AutomatedEmails */
|
||||||
|
private $stats_notifications;
|
||||||
|
|
||||||
|
/** @var MockObject */
|
||||||
|
private $mailer;
|
||||||
|
|
||||||
|
/** @var MockObject */
|
||||||
|
private $renderer;
|
||||||
|
|
||||||
|
/** @var SettingsController */
|
||||||
|
private $settings;
|
||||||
|
|
||||||
|
function _before() {
|
||||||
|
parent::_before();
|
||||||
|
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
|
||||||
|
ScheduledTask::createOrUpdate([
|
||||||
|
'type' => AutomatedEmails::TASK_TYPE,
|
||||||
|
'status' => null,
|
||||||
|
'scheduled_at' => '2017-01-02 12:13:14',
|
||||||
|
'processed_at' => null,
|
||||||
|
]);
|
||||||
|
$this->mailer = $this->createMock(Mailer::class);
|
||||||
|
$this->renderer = $this->createMock(Renderer::class);
|
||||||
|
$this->settings = new SettingsController();
|
||||||
|
$this->stats_notifications = $this->getMockBuilder(AutomatedEmails::class)
|
||||||
|
->enableOriginalConstructor()
|
||||||
|
->setConstructorArgs([
|
||||||
|
$this->mailer,
|
||||||
|
$this->renderer,
|
||||||
|
$this->settings,
|
||||||
|
$this->makeEmpty(WCHelper::class)
|
||||||
|
])
|
||||||
|
->setMethods(['getNewsletters'])
|
||||||
|
->getMock();
|
||||||
|
$this->settings->set(Worker::SETTINGS_KEY, [
|
||||||
|
'automated' => true,
|
||||||
|
'address' => 'email@example.com',
|
||||||
|
]);
|
||||||
|
$this->settings->set('tracking.enabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesntWorkIfDisabled() {
|
||||||
|
$this->settings->set(Worker::SETTINGS_KEY, [
|
||||||
|
'automated' => false,
|
||||||
|
'address' => 'email@example.com',
|
||||||
|
]);
|
||||||
|
expect($this->stats_notifications->checkProcessingRequirements())->equals(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesntWorkIfNoEmail() {
|
||||||
|
$this->settings->set(Worker::SETTINGS_KEY, [
|
||||||
|
'automated' => true,
|
||||||
|
'address' => '',
|
||||||
|
]);
|
||||||
|
expect($this->stats_notifications->checkProcessingRequirements())->equals(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesntWorkIfTrackingIsDisabled() {
|
||||||
|
$this->settings->set('tracking.enabled', false);
|
||||||
|
expect($this->stats_notifications->checkProcessingRequirements())->equals(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesntWorkIfEnabled() {
|
||||||
|
expect($this->stats_notifications->checkProcessingRequirements())->equals(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesntRenderIfNoNewslettersFound() {
|
||||||
|
$this->stats_notifications
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewsletters')
|
||||||
|
->will($this->returnValue([]));
|
||||||
|
$this->renderer->expects($this->never())
|
||||||
|
->method('render');
|
||||||
|
$this->mailer->expects($this->never())
|
||||||
|
->method('send');
|
||||||
|
|
||||||
|
$result = $this->stats_notifications->process();
|
||||||
|
|
||||||
|
expect($result)->equals(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItRenders() {
|
||||||
|
$newsletter1 = Newsletter::create();
|
||||||
|
$newsletter1->hydrate([
|
||||||
|
'id' => 8765,
|
||||||
|
'subject' => 'Subject',
|
||||||
|
'total_sent' => 10,
|
||||||
|
'statistics' => [
|
||||||
|
'clicked' => 5,
|
||||||
|
'opened' => 2,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->stats_notifications
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewsletters')
|
||||||
|
->will($this->returnValue([$newsletter1]));
|
||||||
|
$this->renderer->expects($this->exactly(2))
|
||||||
|
->method('render');
|
||||||
|
$this->renderer->expects($this->at(0))
|
||||||
|
->method('render')
|
||||||
|
->with($this->equalTo('emails/statsNotificationAutomatedEmails.html'));
|
||||||
|
|
||||||
|
$this->renderer->expects($this->at(1))
|
||||||
|
->method('render')
|
||||||
|
->with($this->equalTo('emails/statsNotificationAutomatedEmails.txt'));
|
||||||
|
|
||||||
|
$this->mailer->expects($this->once())
|
||||||
|
->method('send');
|
||||||
|
|
||||||
|
$result = $this->stats_notifications->process();
|
||||||
|
|
||||||
|
expect($result)->equals(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItSends() {
|
||||||
|
$newsletter1 = Newsletter::create();
|
||||||
|
$newsletter1->hydrate([
|
||||||
|
'id' => 8765,
|
||||||
|
'subject' => 'Subject',
|
||||||
|
'total_sent' => 10,
|
||||||
|
'statistics' => [
|
||||||
|
'clicked' => 5,
|
||||||
|
'opened' => 2,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->stats_notifications
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewsletters')
|
||||||
|
->will($this->returnValue([$newsletter1]));
|
||||||
|
|
||||||
|
$this->renderer->expects($this->exactly(2))
|
||||||
|
->method('render');
|
||||||
|
|
||||||
|
$this->mailer->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with(
|
||||||
|
$this->callback(function($rendered_newsletter){
|
||||||
|
return ($rendered_newsletter['subject'] === 'Your monthly stats are in!')
|
||||||
|
&& isset($rendered_newsletter['body']);
|
||||||
|
}),
|
||||||
|
$this->equalTo('email@example.com')
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->stats_notifications->process();
|
||||||
|
|
||||||
|
expect($result)->equals(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItPreparesContext() {
|
||||||
|
$newsletter1 = Newsletter::create();
|
||||||
|
$newsletter1->hydrate([
|
||||||
|
'id' => 8765,
|
||||||
|
'subject' => 'Subject',
|
||||||
|
'total_sent' => 10,
|
||||||
|
'statistics' => [
|
||||||
|
'clicked' => 5,
|
||||||
|
'opened' => 2,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->stats_notifications
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewsletters')
|
||||||
|
->will($this->returnValue([$newsletter1]));
|
||||||
|
$this->renderer->expects($this->exactly(2)) // html + text template
|
||||||
|
->method('render')
|
||||||
|
->with(
|
||||||
|
$this->anything(),
|
||||||
|
$this->callback(function($context){
|
||||||
|
return strpos($context['linkSettings'], 'mailpoet-settings');
|
||||||
|
}));
|
||||||
|
|
||||||
|
$this->stats_notifications->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItAddsNewsletterStatsToContext() {
|
||||||
|
$newsletter1 = Newsletter::create();
|
||||||
|
$newsletter1->hydrate([
|
||||||
|
'id' => 8765,
|
||||||
|
'subject' => 'Subject',
|
||||||
|
'total_sent' => 10,
|
||||||
|
'statistics' => [
|
||||||
|
'clicked' => 5,
|
||||||
|
'opened' => 2,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->stats_notifications
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewsletters')
|
||||||
|
->will($this->returnValue([$newsletter1]));
|
||||||
|
$this->renderer->expects($this->exactly(2)) // html + text template
|
||||||
|
->method('render')
|
||||||
|
->with(
|
||||||
|
$this->anything(),
|
||||||
|
$this->callback(function($context){
|
||||||
|
return strpos($context['newsletters'][0]['linkStats'], 'page=mailpoet-newsletters#/stats')
|
||||||
|
&& $context['newsletters'][0]['clicked'] === 50
|
||||||
|
&& $context['newsletters'][0]['opened'] === 20
|
||||||
|
&& $context['newsletters'][0]['subject'] === 'Subject';
|
||||||
|
}));
|
||||||
|
|
||||||
|
$this->stats_notifications->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
views/emails/statsNotificationAutomatedEmails.html
Normal file
4
views/emails/statsNotificationAutomatedEmails.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<% extends 'emails/statsNotificationLayout.html' %>
|
||||||
|
|
||||||
|
<% block content %>
|
||||||
|
<% endblock %>
|
6
views/emails/statsNotificationAutomatedEmails.txt
Normal file
6
views/emails/statsNotificationAutomatedEmails.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<% extends 'emails/statsNotificationLayout.txt' %>
|
||||||
|
|
||||||
|
<% block content %>
|
||||||
|
|
||||||
|
|
||||||
|
<% endblock %>
|
Reference in New Issue
Block a user