Merge pull request #1728 from mailpoet/stats-notifications
Stats notifications [ MAILPOET-1571]
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
|
||||
use MailPoet\Cron\Workers\WorkersFactory;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Daemon {
|
||||
public $timer;
|
||||
|
||||
function __construct() {
|
||||
/** @var WorkersFactory */
|
||||
private $workers_factory;
|
||||
|
||||
function __construct(WorkersFactory $workers_factory) {
|
||||
$this->timer = microtime(true);
|
||||
$this->workers_factory = $workers_factory;
|
||||
}
|
||||
|
||||
function run($settings_daemon_data) {
|
||||
@@ -22,6 +21,7 @@ class Daemon {
|
||||
CronHelper::saveDaemon($settings_daemon_data);
|
||||
try {
|
||||
$this->executeMigrationWorker();
|
||||
$this->executeStatsNotificationsWorker();
|
||||
$this->executeScheduleWorker();
|
||||
$this->executeQueueWorker();
|
||||
$this->executeSendingServiceKeyCheckWorker();
|
||||
@@ -35,32 +35,37 @@ class Daemon {
|
||||
}
|
||||
|
||||
function executeScheduleWorker() {
|
||||
$scheduler = new SchedulerWorker($this->timer);
|
||||
$scheduler = $this->workers_factory->createScheduleWorker($this->timer);
|
||||
return $scheduler->process();
|
||||
}
|
||||
|
||||
function executeQueueWorker() {
|
||||
$queue = new SendingQueueWorker(new SendingErrorHandler(), $this->timer);
|
||||
$queue = $this->workers_factory->createQueueWorker($this->timer);
|
||||
return $queue->process();
|
||||
}
|
||||
|
||||
function executeStatsNotificationsWorker() {
|
||||
$worker = $this->workers_factory->createStatsNotificationsWorker($this->timer);
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executeSendingServiceKeyCheckWorker() {
|
||||
$worker = new SendingServiceKeyCheckWorker($this->timer);
|
||||
$worker = $this->workers_factory->createSendingServiceKeyCheckWorker($this->timer);
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executePremiumKeyCheckWorker() {
|
||||
$worker = new PremiumKeyCheckWorker($this->timer);
|
||||
$worker = $this->workers_factory->createPremiumKeyCheckWorker($this->timer);
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executeBounceWorker() {
|
||||
$bounce = new BounceWorker($this->timer);
|
||||
$bounce = $this->workers_factory->createBounceWorker($this->timer);
|
||||
return $bounce->process();
|
||||
}
|
||||
|
||||
function executeMigrationWorker() {
|
||||
$migration = new MigrationWorker($this->timer);
|
||||
$migration = $this->workers_factory->createMigrationWorker($this->timer);
|
||||
return $migration->process();
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,7 @@ use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\Worker;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
@@ -44,6 +45,8 @@ class WordPress {
|
||||
$premium_key_specified = Bridge::isPremiumKeySpecified();
|
||||
$premium_keycheck_due_tasks = PremiumKeyCheckWorker::getDueTasks();
|
||||
$premium_keycheck_future_tasks = PremiumKeyCheckWorker::getFutureTasks();
|
||||
// stats notifications
|
||||
$stats_notifications_tasks = (bool)Worker::getDueTasks();
|
||||
// check requirements for each worker
|
||||
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached && !$sending_is_paused);
|
||||
$bounce_sync_active = ($mp_sending_enabled && ($bounce_due_tasks || !$bounce_future_tasks));
|
||||
@@ -57,6 +60,7 @@ class WordPress {
|
||||
|| $bounce_sync_active
|
||||
|| $sending_service_key_check_active
|
||||
|| $premium_key_check_active
|
||||
|| $stats_notifications_tasks
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationsScheduler;
|
||||
use MailPoet\Logging\Logger;
|
||||
use MailPoet\Mailer\MailerError;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
@@ -26,11 +27,15 @@ class SendingQueue {
|
||||
const BATCH_SIZE = 20;
|
||||
const TASK_BATCH_SIZE = 5;
|
||||
|
||||
/** @var StatsNotificationsScheduler */
|
||||
public $stats_notifications_scheduler;
|
||||
|
||||
/** @var SendingErrorHandler */
|
||||
private $error_handler;
|
||||
|
||||
function __construct(SendingErrorHandler $error_handler, $timer = false, $mailer_task = false, $newsletter_task = false) {
|
||||
function __construct(SendingErrorHandler $error_handler, StatsNotificationsScheduler $stats_notifications_scheduler, $timer = false, $mailer_task = false, $newsletter_task = false) {
|
||||
$this->error_handler = $error_handler;
|
||||
$this->stats_notifications_scheduler = $stats_notifications_scheduler;
|
||||
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
|
||||
$this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask();
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
@@ -99,6 +104,7 @@ class SendingQueue {
|
||||
);
|
||||
if($queue->status === ScheduledTaskModel::STATUS_COMPLETED) {
|
||||
$this->newsletter_task->markNewsletterAsSent($newsletter, $queue);
|
||||
$this->stats_notifications_scheduler->schedule($newsletter);
|
||||
}
|
||||
$this->enforceSendingAndExecutionLimits();
|
||||
}
|
||||
|
84
lib/Cron/Workers/StatsNotifications/Scheduler.php
Normal file
84
lib/Cron/Workers/StatsNotifications/Scheduler.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\ScheduledTask;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\StatsNotification;
|
||||
|
||||
class Scheduler {
|
||||
|
||||
/**
|
||||
* How many hours after the newsletter will be the stats notification sent
|
||||
* @var int
|
||||
*/
|
||||
const HOURS_TO_SEND_AFTER_NEWSLETTER = 24;
|
||||
|
||||
function schedule(Newsletter $newsletter) {
|
||||
if(!$this->shouldSchedule($newsletter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$task = ScheduledTask::create();
|
||||
$task->type = Worker::TASK_TYPE;
|
||||
$task->status = ScheduledTask::STATUS_SCHEDULED;
|
||||
$task->scheduled_at = $this->getNextRunDate();
|
||||
$task->save();
|
||||
|
||||
$stats_notifications = StatsNotification::create();
|
||||
$stats_notifications->newsletter_id = $newsletter->id;
|
||||
$stats_notifications->task_id = $task->id;
|
||||
$stats_notifications->save();
|
||||
}
|
||||
|
||||
private function shouldSchedule(Newsletter $newsletter) {
|
||||
if($this->isDisabled()) {
|
||||
return false;
|
||||
}
|
||||
if($this->isTaskScheduled($newsletter->id)) {
|
||||
return false;
|
||||
}
|
||||
if(($newsletter->type !== Newsletter::TYPE_NOTIFICATION) && ($newsletter->type !== Newsletter::TYPE_STANDARD)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isDisabled() {
|
||||
$settings = Setting::getValue(Worker::SETTINGS_KEY);
|
||||
if(!is_array($settings)) {
|
||||
return true;
|
||||
}
|
||||
if(!isset($settings['enabled'])) {
|
||||
return true;
|
||||
}
|
||||
if(!isset($settings['address'])) {
|
||||
return true;
|
||||
}
|
||||
if(empty(trim($settings['address']))) {
|
||||
return true;
|
||||
}
|
||||
if(!(bool)Setting::getValue('tracking.enabled')) {
|
||||
return true;
|
||||
}
|
||||
return !(bool)$settings['enabled'];
|
||||
}
|
||||
|
||||
private function isTaskScheduled($newsletter_id) {
|
||||
$existing = ScheduledTask::table_alias('tasks')
|
||||
->join(StatsNotification::$_table, 'tasks.id = notification.task_id', 'notification')
|
||||
->where('tasks.type', Worker::TASK_TYPE)
|
||||
->where('notification.newsletter_id', $newsletter_id)
|
||||
->findMany();
|
||||
return (bool)$existing;
|
||||
}
|
||||
|
||||
private function getNextRunDate() {
|
||||
$date = new Carbon();
|
||||
$date->addHours(self::HOURS_TO_SEND_AFTER_NEWSLETTER);
|
||||
return $date;
|
||||
}
|
||||
|
||||
}
|
138
lib/Cron/Workers/StatsNotifications/Worker.php
Normal file
138
lib/Cron/Workers/StatsNotifications/Worker.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterLink;
|
||||
use MailPoet\Models\ScheduledTask;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Tasks\Sending;
|
||||
|
||||
class Worker {
|
||||
|
||||
const TASK_TYPE = 'stats_notification';
|
||||
const SETTINGS_KEY = 'stats_notifications';
|
||||
|
||||
const SENDER_EMAIL_PREFIX = 'wordpress@';
|
||||
|
||||
/** @var float */
|
||||
public $timer;
|
||||
|
||||
/** @var Renderer */
|
||||
private $renderer;
|
||||
|
||||
/** @var \MailPoet\Mailer\Mailer */
|
||||
private $mailer;
|
||||
|
||||
function __construct(Mailer $mailer, Renderer $renderer, $timer = false) {
|
||||
$this->timer = $timer ?: microtime(true);
|
||||
$this->renderer = $renderer;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
/** @throws \Exception */
|
||||
function process() {
|
||||
$settings = Setting::getValue(self::SETTINGS_KEY);
|
||||
$this->mailer->sender = $this->mailer->getSenderNameAndAddress($this->constructSenderEmail());
|
||||
foreach(self::getDueTasks() as $task) {
|
||||
try {
|
||||
$this->mailer->send($this->constructNewsletter($task), $settings['address']);
|
||||
} catch(\Exception $e) {
|
||||
if(WP_DEBUG) {
|
||||
throw $e;
|
||||
}
|
||||
} finally {
|
||||
$this->markTaskAsFinished($task);
|
||||
}
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
}
|
||||
}
|
||||
|
||||
private function constructSenderEmail() {
|
||||
$url_parts = parse_url(home_url());
|
||||
$site_name = strtolower($url_parts['host']);
|
||||
if(strpos($site_name, 'www.') === 0) {
|
||||
$site_name = substr($site_name, 4);
|
||||
}
|
||||
return [
|
||||
'address' => self::SENDER_EMAIL_PREFIX . $site_name,
|
||||
'name' => self::SENDER_EMAIL_PREFIX . $site_name,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getDueTasks() {
|
||||
$date = new Carbon();
|
||||
return ScheduledTask::orderByAsc('priority')
|
||||
->orderByAsc('updated_at')
|
||||
->whereNull('deleted_at')
|
||||
->where('status', ScheduledTask::STATUS_SCHEDULED)
|
||||
->whereLte('scheduled_at', $date)
|
||||
->where('type', self::TASK_TYPE)
|
||||
->limit(Sending::RESULT_BATCH_SIZE)
|
||||
->findMany();
|
||||
}
|
||||
|
||||
private function constructNewsletter(ScheduledTask $task) {
|
||||
$newsletter = $this->getNewsletter($task);
|
||||
$link = NewsletterLink::findTopLinkForNewsletter($newsletter);
|
||||
$context = $this->prepareContext($newsletter, $link);
|
||||
return [
|
||||
'subject' => sprintf(_x('Stats for email %s', 'title of an automatic email containing statistics (newsletter open rate, click rate, etc)', 'mailpoet'), $newsletter->subject),
|
||||
'body' => [
|
||||
'html' => $this->renderer->render('emails/statsNotification.html', $context),
|
||||
'text' => $this->renderer->render('emails/statsNotification.txt', $context),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getNewsletter(ScheduledTask $task) {
|
||||
$statsNotificationModel = $task->statsNotification()->findOne();
|
||||
$newsletter = $statsNotificationModel->newsletter()->findOne();
|
||||
if(!$newsletter) {
|
||||
throw new \Exception('Newsletter not found');
|
||||
}
|
||||
return $newsletter
|
||||
->withSendingQueue()
|
||||
->withTotalSent()
|
||||
->withStatistics();
|
||||
}
|
||||
|
||||
private function prepareContext(Newsletter $newsletter, NewsletterLink $link = null) {
|
||||
$clicked = ($newsletter->statistics['clicked'] * 100) / $newsletter->total_sent;
|
||||
$opened = ($newsletter->statistics['opened'] * 100) / $newsletter->total_sent;
|
||||
$unsubscribed = ($newsletter->statistics['unsubscribed'] * 100) / $newsletter->total_sent;
|
||||
$context = [
|
||||
'subject' => $newsletter->subject,
|
||||
'preheader' => sprintf(_x(
|
||||
'%1$s%% opens, %2$s%% clicks, %3$s%% unsubscribes in a nutshell.', 'newsletter open rate, click rate and unsubscribe rate', 'mailpoet'),
|
||||
number_format($opened, 2),
|
||||
number_format($clicked, 2),
|
||||
number_format($unsubscribed, 2)
|
||||
),
|
||||
'topLinkClicks' => 0,
|
||||
'linkSettings' => get_site_url(null, '/wp-admin/admin.php?page=mailpoet-settings#basics'),
|
||||
'linkStats' => get_site_url(null, '/wp-admin/admin.php?page=mailpoet-newsletters#/stats/' . $newsletter->id()),
|
||||
'premiumPage' => get_site_url(null, '/wp-admin/admin.php?page=mailpoet-premium'),
|
||||
'premiumPluginActive' => is_plugin_active('mailpoet-premium/mailpoet-premium.php'),
|
||||
'clicked' => $clicked,
|
||||
'opened' => $opened,
|
||||
];
|
||||
if($link) {
|
||||
$context['topLinkClicks'] = (int)$link->clicksCount;
|
||||
$context['topLink'] = $link->url;
|
||||
}
|
||||
return $context;
|
||||
}
|
||||
|
||||
private function markTaskAsFinished(ScheduledTask $task) {
|
||||
$task->status = ScheduledTask::STATUS_COMPLETED;
|
||||
$task->processed_at = new Carbon;
|
||||
$task->scheduled_at = null;
|
||||
$task->save();
|
||||
}
|
||||
|
||||
}
|
74
lib/Cron/Workers/WorkersFactory.php
Normal file
74
lib/Cron/Workers/WorkersFactory.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationScheduler;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\Worker as StatsNotificationsWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
|
||||
class WorkersFactory {
|
||||
|
||||
/** @var SendingErrorHandler */
|
||||
private $sending_error_handler;
|
||||
|
||||
/** @var StatsNotificationScheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var Mailer */
|
||||
private $mailer;
|
||||
|
||||
/**
|
||||
* @var Renderer
|
||||
*/
|
||||
private $renderer;
|
||||
|
||||
public function __construct(SendingErrorHandler $sending_error_handler, StatsNotificationScheduler $scheduler, Mailer $mailer, Renderer $renderer) {
|
||||
$this->sending_error_handler = $sending_error_handler;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->mailer = $mailer;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/** @return SchedulerWorker */
|
||||
function createScheduleWorker($timer) {
|
||||
return new SchedulerWorker($timer);
|
||||
}
|
||||
|
||||
/** @return SendingQueueWorker */
|
||||
function createQueueWorker($timer) {
|
||||
return new SendingQueueWorker($this->sending_error_handler, $this->scheduler, $timer);
|
||||
}
|
||||
|
||||
function createStatsNotificationsWorker($timer) {
|
||||
return new StatsNotificationsWorker($this->mailer, $this->renderer, $timer);
|
||||
}
|
||||
|
||||
/** @return SendingServiceKeyCheckWorker */
|
||||
function createSendingServiceKeyCheckWorker($timer) {
|
||||
return new SendingServiceKeyCheckWorker($timer);
|
||||
}
|
||||
|
||||
/** @return PremiumKeyCheckWorker */
|
||||
function createPremiumKeyCheckWorker($timer) {
|
||||
return new PremiumKeyCheckWorker($timer);
|
||||
}
|
||||
|
||||
/** @return BounceWorker */
|
||||
function createBounceWorker($timer) {
|
||||
return new BounceWorker($timer);
|
||||
}
|
||||
|
||||
/** @return MigrationWorker */
|
||||
function createMigrationWorker($timer) {
|
||||
return new MigrationWorker($timer);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user