Merge pull request #1728 from mailpoet/stats-notifications

Stats notifications [ MAILPOET-1571]
This commit is contained in:
M. Shull
2019-01-28 13:22:21 -05:00
committed by GitHub
26 changed files with 1459 additions and 95 deletions

View File

@ -86,6 +86,7 @@ class Database {
$statistics_forms = Env::$db_prefix . 'statistics_forms'; $statistics_forms = Env::$db_prefix . 'statistics_forms';
$mapping_to_external_entities = Env::$db_prefix . 'mapping_to_external_entities'; $mapping_to_external_entities = Env::$db_prefix . 'mapping_to_external_entities';
$log = Env::$db_prefix . 'log'; $log = Env::$db_prefix . 'log';
$stats_notifications = Env::$db_prefix . 'stats_notifications';
define('MP_SETTINGS_TABLE', $settings); define('MP_SETTINGS_TABLE', $settings);
define('MP_SEGMENTS_TABLE', $segments); define('MP_SEGMENTS_TABLE', $segments);
@ -112,6 +113,7 @@ class Database {
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms); define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
define('MP_MAPPING_TO_EXTERNAL_ENTITIES_TABLE', $mapping_to_external_entities); define('MP_MAPPING_TO_EXTERNAL_ENTITIES_TABLE', $mapping_to_external_entities);
define('MP_LOG_TABLE', $log); define('MP_LOG_TABLE', $log);
define('MP_STATS_NOTIFICATIONS_TABLE', $stats_notifications);
} }
} }
} }

View File

@ -23,6 +23,7 @@ class Migrator {
'settings', 'settings',
'custom_fields', 'custom_fields',
'scheduled_tasks', 'scheduled_tasks',
'stats_notifications',
'scheduled_task_subscribers', 'scheduled_task_subscribers',
'sending_queues', 'sending_queues',
'subscribers', 'subscribers',
@ -131,12 +132,26 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
} }
function scheduledTaskSubscribers() { function statsNotifications() {
$attributes = array( $attributes = array(
'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
'newsletter_id int(11) unsigned NOT NULL,',
'task_id int(11) unsigned NOT NULL,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_id_task_id (newsletter_id, task_id),',
'KEY task_id (task_id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function scheduledTaskSubscribers() {
$attributes = array (
'task_id int(11) unsigned NOT NULL,', 'task_id int(11) unsigned NOT NULL,',
'subscriber_id int(11) unsigned NOT NULL,', 'subscriber_id int(11) unsigned NOT NULL,',
'processed int(1) NOT NULL,', 'processed int(1) NOT NULL,',
'failed int(1) NOT NULL DEFAULT 0,', 'failed SMALLINT(1) NOT NULL DEFAULT 0,',
'error text NULL,', 'error text NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,', 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (task_id, subscriber_id),', 'PRIMARY KEY (task_id, subscriber_id),',

View File

@ -194,6 +194,15 @@ class Populator {
]); ]);
} }
$stats_notifications = Setting::getValue('stats_notifications');
if(empty($stats_notifications)) {
$sender = Setting::getValue('sender', []);
Setting::setValue('stats_notifications', [
'enabled' => true,
'address' => isset($sender['address'])? $sender['address'] : null,
]);
}
// reset mailer log // reset mailer log
MailerLog::resetMailerLog(); MailerLog::resetMailerLog();
} }

View File

@ -1,20 +1,19 @@
<?php <?php
namespace MailPoet\Cron; namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker; use MailPoet\Cron\Workers\WorkersFactory;
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;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Daemon { class Daemon {
public $timer; public $timer;
function __construct() { /** @var WorkersFactory */
private $workers_factory;
function __construct(WorkersFactory $workers_factory) {
$this->timer = microtime(true); $this->timer = microtime(true);
$this->workers_factory = $workers_factory;
} }
function run($settings_daemon_data) { function run($settings_daemon_data) {
@ -22,6 +21,7 @@ class Daemon {
CronHelper::saveDaemon($settings_daemon_data); CronHelper::saveDaemon($settings_daemon_data);
try { try {
$this->executeMigrationWorker(); $this->executeMigrationWorker();
$this->executeStatsNotificationsWorker();
$this->executeScheduleWorker(); $this->executeScheduleWorker();
$this->executeQueueWorker(); $this->executeQueueWorker();
$this->executeSendingServiceKeyCheckWorker(); $this->executeSendingServiceKeyCheckWorker();
@ -35,32 +35,37 @@ class Daemon {
} }
function executeScheduleWorker() { function executeScheduleWorker() {
$scheduler = new SchedulerWorker($this->timer); $scheduler = $this->workers_factory->createScheduleWorker($this->timer);
return $scheduler->process(); return $scheduler->process();
} }
function executeQueueWorker() { function executeQueueWorker() {
$queue = new SendingQueueWorker(new SendingErrorHandler(), $this->timer); $queue = $this->workers_factory->createQueueWorker($this->timer);
return $queue->process(); return $queue->process();
} }
function executeStatsNotificationsWorker() {
$worker = $this->workers_factory->createStatsNotificationsWorker($this->timer);
return $worker->process();
}
function executeSendingServiceKeyCheckWorker() { function executeSendingServiceKeyCheckWorker() {
$worker = new SendingServiceKeyCheckWorker($this->timer); $worker = $this->workers_factory->createSendingServiceKeyCheckWorker($this->timer);
return $worker->process(); return $worker->process();
} }
function executePremiumKeyCheckWorker() { function executePremiumKeyCheckWorker() {
$worker = new PremiumKeyCheckWorker($this->timer); $worker = $this->workers_factory->createPremiumKeyCheckWorker($this->timer);
return $worker->process(); return $worker->process();
} }
function executeBounceWorker() { function executeBounceWorker() {
$bounce = new BounceWorker($this->timer); $bounce = $this->workers_factory->createBounceWorker($this->timer);
return $bounce->process(); return $bounce->process();
} }
function executeMigrationWorker() { function executeMigrationWorker() {
$migration = new MigrationWorker($this->timer); $migration = $this->workers_factory->createMigrationWorker($this->timer);
return $migration->process(); return $migration->process();
} }

View File

@ -8,6 +8,7 @@ use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
use MailPoet\Cron\Workers\Bounce as BounceWorker; use MailPoet\Cron\Workers\Bounce as BounceWorker;
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker; use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker; use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
use MailPoet\Cron\Workers\StatsNotifications\Worker;
use MailPoet\Mailer\MailerLog; use MailPoet\Mailer\MailerLog;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Services\Bridge; use MailPoet\Services\Bridge;
@ -44,6 +45,8 @@ class WordPress {
$premium_key_specified = Bridge::isPremiumKeySpecified(); $premium_key_specified = Bridge::isPremiumKeySpecified();
$premium_keycheck_due_tasks = PremiumKeyCheckWorker::getDueTasks(); $premium_keycheck_due_tasks = PremiumKeyCheckWorker::getDueTasks();
$premium_keycheck_future_tasks = PremiumKeyCheckWorker::getFutureTasks(); $premium_keycheck_future_tasks = PremiumKeyCheckWorker::getFutureTasks();
// stats notifications
$stats_notifications_tasks = (bool)Worker::getDueTasks();
// check requirements for each worker // check requirements for each worker
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached && !$sending_is_paused); $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)); $bounce_sync_active = ($mp_sending_enabled && ($bounce_due_tasks || !$bounce_future_tasks));
@ -57,6 +60,7 @@ class WordPress {
|| $bounce_sync_active || $bounce_sync_active
|| $sending_service_key_check_active || $sending_service_key_check_active
|| $premium_key_check_active || $premium_key_check_active
|| $stats_notifications_tasks
); );
} }

View File

@ -5,6 +5,7 @@ use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links; use MailPoet\Cron\Workers\SendingQueue\Tasks\Links;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask; use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask; use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationsScheduler;
use MailPoet\Logging\Logger; use MailPoet\Logging\Logger;
use MailPoet\Mailer\MailerError; use MailPoet\Mailer\MailerError;
use MailPoet\Mailer\MailerLog; use MailPoet\Mailer\MailerLog;
@ -26,11 +27,15 @@ class SendingQueue {
const BATCH_SIZE = 20; const BATCH_SIZE = 20;
const TASK_BATCH_SIZE = 5; const TASK_BATCH_SIZE = 5;
/** @var StatsNotificationsScheduler */
public $stats_notifications_scheduler;
/** @var SendingErrorHandler */ /** @var SendingErrorHandler */
private $error_handler; 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->error_handler = $error_handler;
$this->stats_notifications_scheduler = $stats_notifications_scheduler;
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask(); $this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
$this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask(); $this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask();
$this->timer = ($timer) ? $timer : microtime(true); $this->timer = ($timer) ? $timer : microtime(true);
@ -99,6 +104,7 @@ class SendingQueue {
); );
if($queue->status === ScheduledTaskModel::STATUS_COMPLETED) { if($queue->status === ScheduledTaskModel::STATUS_COMPLETED) {
$this->newsletter_task->markNewsletterAsSent($newsletter, $queue); $this->newsletter_task->markNewsletterAsSent($newsletter, $queue);
$this->stats_notifications_scheduler->schedule($newsletter);
} }
$this->enforceSendingAndExecutionLimits(); $this->enforceSendingAndExecutionLimits();
} }

View 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;
}
}

View 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();
}
}

View 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);
}
}

View File

@ -51,9 +51,13 @@ class ContainerConfigurator implements IContainerConfigurator {
// Config // Config
$container->autowire(\MailPoet\Config\AccessControl::class)->setPublic(true); $container->autowire(\MailPoet\Config\AccessControl::class)->setPublic(true);
$container->autowire(\MailPoet\Config\Hooks::class)->setPublic(true); $container->autowire(\MailPoet\Config\Hooks::class)->setPublic(true);
$container->register(\MailPoet\Config\Renderer::class)->setFactory([__CLASS__, 'createRenderer']);
// Cron // Cron
$container->autowire(\MailPoet\Cron\Daemon::class)->setPublic(true); $container->autowire(\MailPoet\Cron\Daemon::class)->setPublic(true);
$container->autowire(\MailPoet\Cron\DaemonHttpRunner::class)->setPublic(true); $container->autowire(\MailPoet\Cron\DaemonHttpRunner::class)->setPublic(true);
$container->autowire(\MailPoet\Cron\Workers\WorkersFactory::class)->setPublic(true);
$container->autowire(\MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler::class)->setPublic(true);
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\Scheduler::class);
// Listing // Listing
$container->autowire(\MailPoet\Listing\BulkActionController::class)->setPublic(true); $container->autowire(\MailPoet\Listing\BulkActionController::class)->setPublic(true);
$container->autowire(\MailPoet\Listing\Handler::class)->setPublic(true); $container->autowire(\MailPoet\Listing\Handler::class)->setPublic(true);
@ -62,6 +66,8 @@ class ContainerConfigurator implements IContainerConfigurator {
$container->autowire(\MailPoet\Router\Endpoints\Subscription::class)->setPublic(true); $container->autowire(\MailPoet\Router\Endpoints\Subscription::class)->setPublic(true);
$container->autowire(\MailPoet\Router\Endpoints\Track::class)->setPublic(true); $container->autowire(\MailPoet\Router\Endpoints\Track::class)->setPublic(true);
$container->autowire(\MailPoet\Router\Endpoints\ViewInBrowser::class)->setPublic(true); $container->autowire(\MailPoet\Router\Endpoints\ViewInBrowser::class)->setPublic(true);
// Mailer
$container->autowire(\MailPoet\Mailer\Mailer::class);
// Subscribers // Subscribers
$container->autowire(\MailPoet\Subscribers\NewSubscriberNotificationMailer::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\NewSubscriberNotificationMailer::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\ConfirmationEmailMailer::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\ConfirmationEmailMailer::class)->setPublic(true);
@ -94,4 +100,10 @@ class ContainerConfigurator implements IContainerConfigurator {
} }
return $container->get(IContainerConfigurator::PREMIUM_CONTAINER_SERVICE_SLUG)->get($id); return $container->get(IContainerConfigurator::PREMIUM_CONTAINER_SERVICE_SLUG)->get($id);
} }
static function createRenderer() {
$caching = !WP_DEBUG;
$debugging = WP_DEBUG;
return new \MailPoet\Config\Renderer($caching, $debugging);
}
} }

View File

@ -5,4 +5,23 @@ if(!defined('ABSPATH')) exit;
class NewsletterLink extends Model { class NewsletterLink extends Model {
public static $_table = MP_NEWSLETTER_LINKS_TABLE; public static $_table = MP_NEWSLETTER_LINKS_TABLE;
static function findTopLinkForNewsletter(Newsletter $newsletter) {
$link = self::selectExpr('links.*')
->selectExpr('count(*)', 'clicksCount')
->tableAlias('links')
->innerJoin(StatisticsClicks::$_table,
array('clicks.link_id', '=', 'links.id'),
'clicks')
->where('newsletter_id', $newsletter->id())
->groupBy('links.id')
->orderByDesc('clicksCount')
->limit(1)
->findOne();
if(!$link) {
return null;
}
return $link;
}
} }

View File

@ -37,6 +37,15 @@ class ScheduledTask extends Model {
); );
} }
/** @return StatsNotification */
function statsNotification() {
return $this->hasOne(
StatsNotification::class,
'task_id',
'id'
);
}
function pause() { function pause() {
$this->set('status', self::STATUS_PAUSED); $this->set('status', self::STATUS_PAUSED);
$this->save(); $this->save();

View File

@ -0,0 +1,42 @@
<?php
namespace MailPoet\Models;
class StatsNotification extends Model {
public static $_table = MP_STATS_NOTIFICATIONS_TABLE;
/** @return Newsletter */
public function newsletter() {
return $this->hasOne(
Newsletter::class,
'id',
'newsletter_id'
);
}
/** @return StatsNotification */
static function createOrUpdate($data = array()) {
$model = null;
if(isset($data['id']) && (int)$data['id'] > 0) {
$model = static::findOne((int)$data['id']);
}
if(!$model && isset($data['task_id']) && $data['newsletter_id']) {
$model = self::where('newsletter_id', $data['newsletter_id'])
->where('task_id', $data['task_id'])
->findOne();
}
if(!$model) {
$model = static::create();
$model->hydrate($data);
} else {
unset($data['id']);
$model->set($data);
}
return $model->save();
}
}

View File

@ -6,6 +6,7 @@ use MailPoet\Config\Renderer;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\WP\Functions;
class NewSubscriberNotificationMailer { class NewSubscriberNotificationMailer {
@ -18,11 +19,15 @@ class NewSubscriberNotificationMailer {
/** @var \MailPoet\Mailer\Mailer */ /** @var \MailPoet\Mailer\Mailer */
private $mailer; private $mailer;
/** @var Functions */
private $wordpress_functions;
/** /**
* @param \MailPoet\Mailer\Mailer|null $mailer * @param \MailPoet\Mailer\Mailer|null $mailer
* @param Renderer|null $renderer * @param Renderer|null $renderer
* @param Functions|null $wordpress_functions
*/ */
function __construct($mailer = null, $renderer = null) { function __construct($mailer = null, $renderer = null, $wordpress_functions = null) {
if($renderer) { if($renderer) {
$this->renderer = $renderer; $this->renderer = $renderer;
} else { } else {
@ -30,6 +35,11 @@ class NewSubscriberNotificationMailer {
$debugging = WP_DEBUG; $debugging = WP_DEBUG;
$this->renderer = new Renderer($caching, $debugging); $this->renderer = new Renderer($caching, $debugging);
} }
if($wordpress_functions) {
$this->wordpress_functions = $wordpress_functions;
} else {
$this->wordpress_functions = new Functions();
}
if($mailer) { if($mailer) {
$this->mailer = $mailer; $this->mailer = $mailer;
} else { } else {
@ -75,7 +85,7 @@ class NewSubscriberNotificationMailer {
} }
private function constructSenderEmail() { private function constructSenderEmail() {
$url_parts = parse_url(home_url()); $url_parts = parse_url($this->wordpress_functions->homeUrl());
$site_name = strtolower($url_parts['host']); $site_name = strtolower($url_parts['host']);
if(substr($site_name, 0, 4) === 'www.') { if(substr($site_name, 0, 4) === 'www.') {
$site_name = substr($site_name, 4); $site_name = substr($site_name, 4);

View File

@ -28,6 +28,10 @@ class Functions {
return call_user_func_array('current_time', func_get_args()); return call_user_func_array('current_time', func_get_args());
} }
function homeUrl() {
return call_user_func_array('home_url', func_get_args());
}
function getImageInfo($id) { function getImageInfo($id) {
/* /*
* In some cases wp_get_attachment_image_src ignore the second parameter * In some cases wp_get_attachment_image_src ignore the second parameter

View File

@ -17,6 +17,10 @@ class CronHelperTest extends \MailPoetTest {
Setting::setValue('cron_trigger', array( Setting::setValue('cron_trigger', array(
'method' => 'none' 'method' => 'none'
)); ));
Setting::setValue('sender', array(
'name' => 'John Doe',
'address' => 'john.doe@example.com'
));
} }
function testItDefinesConstants() { function testItDefinesConstants() {

View File

@ -6,6 +6,8 @@ use Codeception\Stub\Expected;
use MailPoet\Cron\CronHelper; use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Daemon; use MailPoet\Cron\Daemon;
use MailPoet\Cron\DaemonHttpRunner; use MailPoet\Cron\DaemonHttpRunner;
use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
use MailPoet\Cron\Workers\WorkersFactory;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
class DaemonHttpRunnerTest extends \MailPoetTest { class DaemonHttpRunnerTest extends \MailPoetTest {
@ -20,27 +22,25 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
} }
function testItDoesNotRunWithoutRequestData() { function testItDoesNotRunWithoutRequestData() {
$daemon = Stub::construct( $daemon = Stub::make(
new DaemonHttpRunner(new Daemon()), DaemonHttpRunner::class,
array(), [
array(
'abortWithError' => function($message) { 'abortWithError' => function($message) {
return $message; return $message;
} }
) ]
); );
expect($daemon->run(false))->equals('Invalid or missing request data.'); expect($daemon->run(false))->equals('Invalid or missing request data.');
} }
function testItDoesNotRunWhenThereIsInvalidOrMissingToken() { function testItDoesNotRunWhenThereIsInvalidOrMissingToken() {
$daemon = Stub::construct( $daemon = Stub::make(
new DaemonHttpRunner(new Daemon()), DaemonHttpRunner::class,
array(), [
array(
'abortWithError' => function($message) { 'abortWithError' => function($message) {
return $message; return $message;
} }
) ]
); );
$daemon->settings_daemon_data = array( $daemon->settings_daemon_data = array(
'token' => 123 'token' => 123
@ -52,14 +52,19 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
$data = array( $data = array(
'token' => 123 'token' => 123
); );
$daemon = Stub::make(new Daemon(), array( $daemon = Stub::make(
'executeScheduleWorker' => function() { Daemon::class,
throw new \Exception('Message'); [
}, 'executeScheduleWorker' => function() {
'executeQueueWorker' => function() { throw new \Exception('Message');
throw new \Exception(); },
}, 'executeQueueWorker' => function() {
), $this); throw new \Exception();
},
'executeMigrationWorker' => null,
'executeStatsNotificationsWorker' => null,
]
);
$daemon_http_runner = Stub::make(new DaemonHttpRunner($daemon), array( $daemon_http_runner = Stub::make(new DaemonHttpRunner($daemon), array(
'pauseExecution' => null, 'pauseExecution' => null,
'callSelf' => null 'callSelf' => null
@ -72,16 +77,14 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
} }
function testItCanPauseExecution() { function testItCanPauseExecution() {
$daemon = Stub::make(new Daemon(), array( $daemon = Stub::makeEmpty(Daemon::class);
'executeScheduleWorker' => null, $daemon_http_runner = Stub::make(DaemonHttpRunner::class, array(
'executeQueueWorker' => null,
), $this);
$daemon_http_runner = Stub::make(new DaemonHttpRunner($daemon), array(
'pauseExecution' => Expected::exactly(1, function($pause_delay) { 'pauseExecution' => Expected::exactly(1, function($pause_delay) {
expect($pause_delay)->lessThan(CronHelper::DAEMON_EXECUTION_LIMIT); expect($pause_delay)->lessThan(CronHelper::DAEMON_EXECUTION_LIMIT);
expect($pause_delay)->greaterThan(CronHelper::DAEMON_EXECUTION_LIMIT - 1); expect($pause_delay)->greaterThan(CronHelper::DAEMON_EXECUTION_LIMIT - 1);
}), }),
'callSelf' => null 'callSelf' => null,
'terminateRequest' => null,
), $this); ), $this);
$data = array( $data = array(
'token' => 123 'token' => 123
@ -93,7 +96,7 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
function testItTerminatesExecutionWhenDaemonIsDeleted() { function testItTerminatesExecutionWhenDaemonIsDeleted() {
$daemon = Stub::make(new DaemonHttpRunner(new Daemon()), array( $daemon = Stub::make(DaemonHttpRunner::class, array(
'executeScheduleWorker' => function() { 'executeScheduleWorker' => function() {
Setting::deleteValue(CronHelper::DAEMON_SETTING); Setting::deleteValue(CronHelper::DAEMON_SETTING);
}, },
@ -105,12 +108,12 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct(new Daemon()); $daemon->__construct(Stub::makeEmpty(Daemon::class));
$daemon->run($data); $daemon->run($data);
} }
function testItTerminatesExecutionWhenDaemonTokenChangesAndKeepsChangedToken() { function testItTerminatesExecutionWhenDaemonTokenChangesAndKeepsChangedToken() {
$daemon = Stub::make(new DaemonHttpRunner(new Daemon()), array( $daemon = Stub::make(DaemonHttpRunner::class, array(
'executeScheduleWorker' => function() { 'executeScheduleWorker' => function() {
Setting::setValue( Setting::setValue(
CronHelper::DAEMON_SETTING, CronHelper::DAEMON_SETTING,
@ -125,14 +128,14 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct(new Daemon()); $daemon->__construct(Stub::makeEmpty(Daemon::class));
$daemon->run($data); $daemon->run($data);
$data_after_run = Setting::getValue(CronHelper::DAEMON_SETTING); $data_after_run = Setting::getValue(CronHelper::DAEMON_SETTING);
expect($data_after_run['token'], 567); expect($data_after_run['token'], 567);
} }
function testItTerminatesExecutionWhenDaemonIsDeactivated() { function testItTerminatesExecutionWhenDaemonIsDeactivated() {
$daemon = Stub::make(new DaemonHttpRunner(new Daemon()), [ $daemon = Stub::make(DaemonHttpRunner::class, [
'executeScheduleWorker' => null, 'executeScheduleWorker' => null,
'executeQueueWorker' => null, 'executeQueueWorker' => null,
'pauseExecution' => null, 'pauseExecution' => null,
@ -143,34 +146,40 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
'status' => CronHelper::DAEMON_STATUS_INACTIVE, 'status' => CronHelper::DAEMON_STATUS_INACTIVE,
]; ];
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct(new Daemon()); $daemon->__construct(Stub::makeEmpty(Daemon::class));
$daemon->run($data); $daemon->run($data);
} }
function testItUpdatesDaemonTokenDuringExecution() { function testItUpdatesDaemonTokenDuringExecution() {
$daemon_http_runner = Stub::make(new DaemonHttpRunner(new Daemon()), array( $daemon_http_runner = Stub::make(DaemonHttpRunner::class, array(
'executeScheduleWorker' => null, 'executeScheduleWorker' => null,
'executeQueueWorker' => null, 'executeQueueWorker' => null,
'pauseExecution' => null, 'pauseExecution' => null,
'callSelf' => null 'callSelf' => null,
'terminateRequest' => null,
), $this); ), $this);
$data = array( $data = array(
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon_http_runner->__construct(new Daemon()); $daemon_http_runner->__construct(Stub::makeEmptyExcept(Daemon::class, 'run'));
$daemon_http_runner->run($data); $daemon_http_runner->run($data);
$updated_daemon = Setting::getValue(CronHelper::DAEMON_SETTING); $updated_daemon = Setting::getValue(CronHelper::DAEMON_SETTING);
expect($updated_daemon['token'])->equals($daemon_http_runner->token); expect($updated_daemon['token'])->equals($daemon_http_runner->token);
} }
function testItUpdatesTimestampsDuringExecution() { function testItUpdatesTimestampsDuringExecution() {
$daemon = Stub::make(new Daemon(), array( $daemon = Stub::make(Daemon::class, [
'executeScheduleWorker' => function() { 'executeScheduleWorker' => function() {
sleep(2); sleep(2);
}, },
'executeQueueWorker' => null, 'executeQueueWorker' => function() {
), $this); throw new \Exception();
},
'executeMigrationWorker' => null,
'executeStatsNotificationsWorker' => null,
]
);
$daemon_http_runner = Stub::make(new DaemonHttpRunner($daemon), array( $daemon_http_runner = Stub::make(new DaemonHttpRunner($daemon), array(
'pauseExecution' => null, 'pauseExecution' => null,
'callSelf' => null 'callSelf' => null
@ -192,25 +201,26 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
function testItCanRun() { function testItCanRun() {
ignore_user_abort(0); ignore_user_abort(0);
expect(ignore_user_abort())->equals(0); expect(ignore_user_abort())->equals(0);
$daemon = Stub::make(new DaemonHttpRunner(new Daemon()), array( $daemon = Stub::make(DaemonHttpRunner::class, array(
'pauseExecution' => null, 'pauseExecution' => null,
// workers should be executed // workers should be executed
'executeScheduleWorker' => Expected::exactly(1), 'executeScheduleWorker' => Expected::exactly(1),
'executeQueueWorker' => Expected::exactly(1), 'executeQueueWorker' => Expected::exactly(1),
// daemon should call itself // daemon should call itself
'callSelf' => Expected::exactly(1), 'callSelf' => Expected::exactly(1),
'terminateRequest' => null,
), $this); ), $this);
$data = array( $data = array(
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct(new Daemon()); $daemon->__construct(Stub::makeEmptyExcept(Daemon::class, 'run'));
$daemon->run($data); $daemon->run($data);
expect(ignore_user_abort())->equals(1); expect(ignore_user_abort())->equals(1);
} }
function testItRespondsToPingRequest() { function testItRespondsToPingRequest() {
$daemon = Stub::make(new DaemonHttpRunner(new Daemon()), array( $daemon = Stub::make(DaemonHttpRunner::class, array(
'terminateRequest' => Expected::exactly(1, function($message) { 'terminateRequest' => Expected::exactly(1, function($message) {
expect($message)->equals('pong'); expect($message)->equals('pong');
}) })

View File

@ -6,39 +6,44 @@ use Codeception\Stub\Expected;
use MailPoet\Cron\CronHelper; use MailPoet\Cron\CronHelper;
use MailPoet\Cron\DaemonHttpRunner; use MailPoet\Cron\DaemonHttpRunner;
use MailPoet\Cron\Daemon; use MailPoet\Cron\Daemon;
use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
use MailPoet\Cron\Workers\WorkersFactory;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
class DaemonTest extends \MailPoetTest { class DaemonTest extends \MailPoetTest {
function testItCanExecuteWorkers() { function testItCanExecuteWorkers() {
$daemon = Stub::make(new Daemon(), array( $daemon = Stub::make(Daemon::class, array(
'executeScheduleWorker' => Expected::exactly(1), 'executeScheduleWorker' => Expected::exactly(1),
'executeQueueWorker' => Expected::exactly(1), 'executeQueueWorker' => Expected::exactly(1),
'pauseExecution' => null, 'executeMigrationWorker' => null,
'callSelf' => null 'executeStatsNotificationsWorker' => null,
'executeSendingServiceKeyCheckWorker' => null,
'executePremiumKeyCheckWorker' => null,
'executeBounceWorker' => null,
), $this); ), $this);
$data = array( $data = array(
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct($data);
$daemon->run([]); $daemon->run([]);
} }
function testItCanRun() { function testItCanRun() {
$daemon = Stub::make(new Daemon(), array( $daemon = Stub::make(Daemon::class, array(
'pauseExecution' => null,
// workers should be executed // workers should be executed
'executeScheduleWorker' => Expected::exactly(1), 'executeScheduleWorker' => Expected::exactly(1),
'executeQueueWorker' => Expected::exactly(1), 'executeQueueWorker' => Expected::exactly(1),
// daemon should call itself 'executeMigrationWorker' => Expected::exactly(1),
'callSelf' => Expected::exactly(1), 'executeStatsNotificationsWorker' => Expected::exactly(1),
'executeSendingServiceKeyCheckWorker' => Expected::exactly(1),
'executePremiumKeyCheckWorker' => Expected::exactly(1),
'executeBounceWorker' => Expected::exactly(1)
), $this); ), $this);
$data = array( $data = array(
'token' => 123 'token' => 123
); );
Setting::setValue(CronHelper::DAEMON_SETTING, $data); Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct();
$daemon->run($data); $daemon->run($data);
} }

View File

@ -12,6 +12,7 @@ use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker; use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask; use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask; use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationsScheduler;
use MailPoet\Mailer\MailerLog; use MailPoet\Mailer\MailerLog;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink; use MailPoet\Models\NewsletterLink;
@ -36,6 +37,9 @@ class SendingQueueTest extends \MailPoetTest {
/** @var SendingErrorHandler */ /** @var SendingErrorHandler */
private $sending_error_handler; private $sending_error_handler;
/** @var Scheduler */
private $stats_notifications_worker;
function _before() { function _before() {
$wp_users = get_users(); $wp_users = get_users();
wp_set_current_user($wp_users[0]->ID); wp_set_current_user($wp_users[0]->ID);
@ -76,7 +80,8 @@ class SendingQueueTest extends \MailPoetTest {
$this->newsletter_link->hash = 'abcde'; $this->newsletter_link->hash = 'abcde';
$this->newsletter_link->save(); $this->newsletter_link->save();
$this->sending_error_handler = new SendingErrorHandler(); $this->sending_error_handler = new SendingErrorHandler();
$this->sending_queue_worker = new SendingQueueWorker($this->sending_error_handler); $this->stats_notifications_worker = new StatsNotificationsScheduler();
$this->sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker);
} }
private function getDirectUnsubscribeURL() { private function getDirectUnsubscribeURL() {
@ -106,20 +111,20 @@ class SendingQueueTest extends \MailPoetTest {
// constructor accepts timer argument // constructor accepts timer argument
$timer = microtime(true) - 5; $timer = microtime(true) - 5;
$sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $timer); $sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker, $timer);
expect($sending_queue_worker->timer)->equals($timer); expect($sending_queue_worker->timer)->equals($timer);
} }
function testItEnforcesExecutionLimitsBeforeQueueProcessing() { function testItEnforcesExecutionLimitsBeforeQueueProcessing() {
$sending_queue_worker = Stub::make( $sending_queue_worker = Stub::make(
new SendingQueueWorker($this->sending_error_handler), new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker),
array( array(
'processQueue' => Expected::never(), 'processQueue' => Expected::never(),
'enforceSendingAndExecutionLimits' => Expected::exactly(1, function() { 'enforceSendingAndExecutionLimits' => Expected::exactly(1, function() {
throw new \Exception(); throw new \Exception();
}) })
), $this); ), $this);
$sending_queue_worker->__construct($this->sending_error_handler); $sending_queue_worker->__construct($this->sending_error_handler, $this->stats_notifications_worker);
try { try {
$sending_queue_worker->process(); $sending_queue_worker->process();
self::fail('Execution limits function was not called.'); self::fail('Execution limits function was not called.');
@ -130,12 +135,13 @@ class SendingQueueTest extends \MailPoetTest {
function testItEnforcesExecutionLimitsAfterSendingWhenQueueStatusIsNotSetToComplete() { function testItEnforcesExecutionLimitsAfterSendingWhenQueueStatusIsNotSetToComplete() {
$sending_queue_worker = Stub::make( $sending_queue_worker = Stub::make(
new SendingQueueWorker($this->sending_error_handler), new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker),
array( array(
'enforceSendingAndExecutionLimits' => Expected::exactly(1) 'enforceSendingAndExecutionLimits' => Expected::exactly(1)
), $this); ), $this);
$sending_queue_worker->__construct( $sending_queue_worker->__construct(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -164,12 +170,13 @@ class SendingQueueTest extends \MailPoetTest {
$queue = $this->queue; $queue = $this->queue;
$queue->status = SendingQueue::STATUS_COMPLETED; $queue->status = SendingQueue::STATUS_COMPLETED;
$sending_queue_worker = Stub::make( $sending_queue_worker = Stub::make(
new SendingQueueWorker($this->sending_error_handler), new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker),
array( array(
'enforceSendingAndExecutionLimits' => Expected::never() 'enforceSendingAndExecutionLimits' => Expected::never()
), $this); ), $this);
$sending_queue_worker->__construct( $sending_queue_worker->__construct(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -193,7 +200,7 @@ class SendingQueueTest extends \MailPoetTest {
function testItEnforcesExecutionLimitsAfterQueueProcessing() { function testItEnforcesExecutionLimitsAfterQueueProcessing() {
$sending_queue_worker = Stub::make( $sending_queue_worker = Stub::make(
new SendingQueueWorker($this->sending_error_handler), new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker),
array( array(
'processQueue' => function() { 'processQueue' => function() {
// this function returns a queue object // this function returns a queue object
@ -201,7 +208,7 @@ class SendingQueueTest extends \MailPoetTest {
}, },
'enforceSendingAndExecutionLimits' => Expected::exactly(2) 'enforceSendingAndExecutionLimits' => Expected::exactly(2)
), $this); ), $this);
$sending_queue_worker->__construct($this->sending_error_handler); $sending_queue_worker->__construct($this->sending_error_handler, $this->stats_notifications_worker);
$sending_queue_worker->process(); $sending_queue_worker->process();
} }
@ -225,6 +232,7 @@ class SendingQueueTest extends \MailPoetTest {
$directUnsubscribeURL = $this->getDirectUnsubscribeURL(); $directUnsubscribeURL = $this->getDirectUnsubscribeURL();
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -246,6 +254,7 @@ class SendingQueueTest extends \MailPoetTest {
$trackedUnsubscribeURL = $this->getTrackedUnsubscribeURL(); $trackedUnsubscribeURL = $this->getTrackedUnsubscribeURL();
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -265,6 +274,7 @@ class SendingQueueTest extends \MailPoetTest {
function testItCanProcessSubscribersOneByOne() { function testItCanProcessSubscribersOneByOne() {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -309,6 +319,7 @@ class SendingQueueTest extends \MailPoetTest {
function testItCanProcessSubscribersInBulk() { function testItCanProcessSubscribersInBulk() {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -356,6 +367,7 @@ class SendingQueueTest extends \MailPoetTest {
function testItProcessesStandardNewsletters() { function testItProcessesStandardNewsletters() {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -410,6 +422,7 @@ class SendingQueueTest extends \MailPoetTest {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::makeEmpty(new MailerTask(), array(), $this) Stub::makeEmpty(new MailerTask(), array(), $this)
); );
@ -425,6 +438,7 @@ class SendingQueueTest extends \MailPoetTest {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -591,9 +605,10 @@ class SendingQueueTest extends \MailPoetTest {
'updateProcessedSubscribers' => false 'updateProcessedSubscribers' => false
)); ));
$sending_task->id = 100; $sending_task->id = 100;
$sending_queue_worker = Stub::make(new SendingQueueWorker($this->sending_error_handler)); $sending_queue_worker = Stub::make(new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker));
$sending_queue_worker->__construct( $sending_queue_worker->__construct(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -627,6 +642,7 @@ class SendingQueueTest extends \MailPoetTest {
function testItDoesNotUpdateNewsletterHashDuringSending() { function testItDoesNotUpdateNewsletterHashDuringSending() {
$sending_queue_worker = new SendingQueueWorker( $sending_queue_worker = new SendingQueueWorker(
$this->sending_error_handler, $this->sending_error_handler,
$this->stats_notifications_worker,
$timer = false, $timer = false,
Stub::make( Stub::make(
new MailerTask(), new MailerTask(),
@ -650,7 +666,7 @@ class SendingQueueTest extends \MailPoetTest {
return $custom_batch_size_value; return $custom_batch_size_value;
}; };
Hooks::addFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter); Hooks::addFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter);
$sending_queue_worker = new SendingQueueWorker($this->sending_error_handler); $sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker);
expect($sending_queue_worker->batch_size)->equals($custom_batch_size_value); expect($sending_queue_worker->batch_size)->equals($custom_batch_size_value);
Hooks::removeFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter); Hooks::removeFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter);
} }

View File

@ -0,0 +1,116 @@
<?php
namespace MailPoet\Cron\Workers\StatsNotifications;
use MailPoet\Models\Newsletter;
use MailPoet\Models\ScheduledTask;
use MailPoet\Models\Setting;
use MailPoet\Models\StatsNotification;
class SchedulerTest extends \MailPoetTest {
/** @var Scheduler */
private $stats_notifications;
function _before() {
$this->stats_notifications = new Scheduler();
Setting::setValue(Worker::SETTINGS_KEY, [
'enabled' => true,
'address' => 'email@example.com'
]);
Setting::setValue('tracking.enabled', true);
}
function testShouldSchedule() {
$newsletter_id = 5;
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isInstanceOf(StatsNotification::class);
$task = ScheduledTask::where('id', $notification->task_id)->findOne();
expect($task)->isInstanceOf(ScheduledTask::class);
}
function testShouldNotScheduleIfTrackingIsDisabled() {
Setting::setValue('tracking.enabled', false);
$newsletter_id = 13;
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
function testShouldNotScheduleIfDisabled() {
$newsletter_id = 6;
Setting::setValue(Worker::SETTINGS_KEY, [
'enabled' => false,
'address' => 'email@example.com'
]);
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
function testShouldNotScheduleIfSettingsMissing() {
$newsletter_id = 7;
Setting::setValue(Worker::SETTINGS_KEY, []);
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
function testShouldNotScheduleIfEmailIsMissing() {
$newsletter_id = 8;
Setting::setValue(Worker::SETTINGS_KEY, [
'enabled' => true,
]);
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
function testShouldNotScheduleIfEmailIsEmpty() {
$newsletter_id = 9;
Setting::setValue(Worker::SETTINGS_KEY, [
'enabled' => true,
'address' => ' '
]);
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
function testShouldNotScheduleIfAlreadyScheduled() {
$newsletter_id = 10;
$existing_task = ScheduledTask::createOrUpdate([
'type' => Worker::TASK_TYPE,
'status' => ScheduledTask::STATUS_SCHEDULED,
'scheduled_at' => '2017-01-02 12:13:14',
]);
$existing_notification = StatsNotification::createOrUpdate([
'newsletter_id' => $newsletter_id,
'task_id' => $existing_task->id,
]);
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
$this->stats_notifications->schedule($newsletter);
$notifications = StatsNotification::where('newsletter_id', $newsletter_id)->findMany();
expect($notifications)->count(1);
$tasks = ScheduledTask::where('id', $notifications[0]->task_id)->findMany();
expect($tasks)->count(1);
expect($existing_notification->id)->equals($notifications[0]->id);
expect($existing_task->id)->equals($tasks[0]->id);
}
function testShouldNotScheduleIfInvalidType() {
$newsletter_id = 11;
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, Newsletter::TYPE_WELCOME]);
$this->stats_notifications->schedule($newsletter);
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
expect($notification)->isEmpty();
}
}

View File

@ -0,0 +1,224 @@
<?php
namespace MailPoet\Cron\Workers\StatsNotifications;
use MailPoet\Config\Renderer;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\ScheduledTask;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsClicks;
use MailPoet\Models\StatisticsOpens;
use MailPoet\Models\StatisticsUnsubscribes;
use MailPoet\Models\StatsNotification;
use PHPUnit\Framework\MockObject\MockObject;
class WorkerTest extends \MailPoetTest {
/** @var Worker */
private $stats_notifications;
/** @var MockObject */
private $mailer;
/** @var MockObject */
private $renderer;
function _before() {
\ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
\ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
\ORM::raw_execute('TRUNCATE ' . StatsNotification::$_table);
$this->mailer = $this->createMock(Mailer::class);
$this->renderer = $this->createMock(Renderer::class);
$this->stats_notifications = new Worker($this->mailer, $this->renderer);
Setting::setValue(Worker::SETTINGS_KEY, [
'enabled' => true,
'address' => 'email@example.com'
]);
$newsletter = Newsletter::createOrUpdate([
'subject' => 'Email Subject1',
'type' => Newsletter::TYPE_STANDARD,
]);
$sending_task = ScheduledTask::createOrUpdate([
'type' => 'sending',
'status' => ScheduledTask::STATUS_COMPLETED,
]);
$stats_notifications_task = ScheduledTask::createOrUpdate([
'type' => Worker::TASK_TYPE,
'status' => ScheduledTask::STATUS_SCHEDULED,
'scheduled_at' => '2017-01-02 12:13:14',
'processed_at' => null,
]);
StatsNotification::createOrUpdate([
'newsletter_id' => $newsletter->id(),
'task_id' => $stats_notifications_task->id(),
]);
$queue = SendingQueue::createOrUpdate([
'newsletter_rendered_subject' => 'Email Subject',
'task_id' => $sending_task->id(),
'newsletter_id' => $newsletter->id(),
'count_processed' => 5,
]);
$link = NewsletterLink::createOrUpdate([
'url' => 'Link url',
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'hash' => 'xyz',
]);
StatisticsClicks::createOrUpdate([
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'subscriber_id' => '5',
'link_id' => $link->id(),
'count' => 5,
'created_at' => '2018-01-02 15:16:17',
]);
$link2 = NewsletterLink::createOrUpdate([
'url' => 'Link url2',
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'hash' => 'xyzd',
]);
StatisticsClicks::createOrUpdate([
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'subscriber_id' => '6',
'link_id' => $link2->id(),
'count' => 5,
'created_at' => '2018-01-02 15:16:17',
]);
StatisticsClicks::createOrUpdate([
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'subscriber_id' => '7',
'link_id' => $link2->id(),
'count' => 5,
'created_at' => '2018-01-02 15:16:17',
]);
StatisticsOpens::createOrUpdate([
'subscriber_id' => '10',
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'created_at' => '2017-01-02 12:23:45',
]);
StatisticsOpens::createOrUpdate([
'subscriber_id' => '11',
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'created_at' => '2017-01-02 21:23:45',
]);
StatisticsUnsubscribes::createOrUpdate([
'subscriber_id' => '12',
'newsletter_id' => $newsletter->id(),
'queue_id' => $queue->id(),
'created_at' => '2017-01-02 21:23:45',
]);
}
function testRendersTemplate() {
$this->renderer->expects($this->exactly(2))
->method('render');
$this->renderer->expects($this->at(0))
->method('render')
->with($this->equalTo('emails/statsNotification.html'));
$this->renderer->expects($this->at(1))
->method('render')
->with($this->equalTo('emails/statsNotification.txt'));
$this->stats_notifications->process();
}
function testAddsSubjectToContext() {
$this->renderer->expects($this->exactly(2)) // html + text template
->method('render')
->with(
$this->anything(),
$this->callback(function($context){
return $context['subject'] === 'Email Subject1';
}));
$this->stats_notifications->process();
}
function testAddsPreHeaderToContext() {
$this->renderer->expects($this->exactly(2)) // html + text template
->method('render')
->with(
$this->anything(),
$this->callback(function($context){
return $context['preheader'] === '40.00% opens, 60.00% clicks, 20.00% unsubscribes in a nutshell.';
}));
$this->stats_notifications->process();
}
function testAddsWPUrlsToContext() {
$this->renderer->expects($this->exactly(2)) // html + text template
->method('render')
->with(
$this->anything(),
$this->callback(function($context){
return strpos($context['linkSettings'], 'mailpoet-settings')
&& strpos($context['linkStats'], 'mailpoet-newsletters#/stats');
}));
$this->stats_notifications->process();
}
function testAddsLinksToContext() {
$this->renderer->expects($this->exactly(2)) // html + text template
->method('render')
->with(
$this->anything(),
$this->callback(function($context){
return ($context['topLink'] === 'Link url2')
&& ($context['topLinkClicks'] === 2);
}));
$this->stats_notifications->process();
}
function testSends() {
$this->mailer->expects($this->once())
->method('send');
$this->stats_notifications->process();
}
function testItWorksForNewsletterWithNoStats() {
$newsletter = Newsletter::createOrUpdate([
'subject' => 'Email Subject2',
'type' => Newsletter::TYPE_STANDARD,
]);
$sending_task = ScheduledTask::createOrUpdate([
'type' => 'sending',
'status' => ScheduledTask::STATUS_COMPLETED,
]);
$stats_notifications_task = ScheduledTask::createOrUpdate([
'type' => Worker::TASK_TYPE,
'status' => ScheduledTask::STATUS_SCHEDULED,
'scheduled_at' => '2016-01-02 12:13:14',
'processed_at' => null,
]);
StatsNotification::createOrUpdate([
'newsletter_id' => $newsletter->id(),
'task_id' => $stats_notifications_task->id(),
]);
SendingQueue::createOrUpdate([
'newsletter_rendered_subject' => 'Email Subject2',
'task_id' => $sending_task->id(),
'newsletter_id' => $newsletter->id(),
'count_processed' => 15,
]);
$this->mailer->expects($this->exactly(2))
->method('send');
$this->stats_notifications->process();
}
}

View File

@ -8,6 +8,7 @@ use MailPoet\Mailer\Mailer;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\WP\Functions;
class NewSubscriberNotificationMailerTest extends \MailPoetTest { class NewSubscriberNotificationMailerTest extends \MailPoetTest {
@ -85,7 +86,6 @@ class NewSubscriberNotificationMailerTest extends \MailPoetTest {
function testItRemovesWwwFromSenderAddress() { function testItRemovesWwwFromSenderAddress() {
Setting::setValue(NewSubscriberNotificationMailer::SETTINGS_KEY, ['enabled' => true,'address' => 'a@b.c']); Setting::setValue(NewSubscriberNotificationMailer::SETTINGS_KEY, ['enabled' => true,'address' => 'a@b.c']);
update_option( 'home', 'http://www.example.com/xyz' );
$mailer = Stub::makeEmpty(Mailer::class, [ $mailer = Stub::makeEmpty(Mailer::class, [
'getSenderNameAndAddress' => 'getSenderNameAndAddress' =>
@ -96,7 +96,14 @@ class NewSubscriberNotificationMailerTest extends \MailPoetTest {
}), }),
], $this); ], $this);
$service = new NewSubscriberNotificationMailer($mailer); $functions = Stub::makeEmpty(Functions::class, [
'homeUrl' =>
Expected::once(function() {
return 'http://www.example.com/xyz';
}),
], $this);
$service = new NewSubscriberNotificationMailer($mailer, null, $functions);
$service->send($this->subscriber, $this->segments); $service->send($this->subscriber, $this->segments);
} }
} }

View File

@ -0,0 +1,456 @@
<html lang="en" style="margin:0;padding:0">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="format-detection" content="telephone=no" />
<title><%= subject %></title>
<style type="text/css"> @media screen and (max-width: 480px) {
.mailpoet_button {width:100% !important;}
}
@media screen and (max-width: 599px) {
.mailpoet_header {
padding: 10px 20px;
}
.mailpoet_button {
width: 100% !important;
padding: 5px 0 !important;
box-sizing:border-box !important;
}
div, .mailpoet_cols-two {
max-width: 100% !important;
}
}
</style>
</head>
<% if opened > 30 %>
<% set openedColor = '2993ab' %>
<% elseif opened > 10 %>
<% set openedColor = 'f0b849' %>
<% else %>
<% set openedColor = 'd54e21' %>
<% endif %>
<% if clicked > 3 %>
<% set clickedColor = '2993ab' %>
<% elseif clicked > 1 %>
<% set clickedColor = 'f0b849' %>
<% else %>
<% set clickedColor = 'd54e21' %>
<% endif %>
<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0" style="margin:0;padding:0;background-color:#f0f0f0">
<table class="mailpoet_template" border="0" width="100%" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_preheader" style="-webkit-text-size-adjust:none;font-size:1px;line-height:1px;color:#ffffff;border-collapse:collapse;display:none;visibility:hidden;mso-hide:all;max-height:0;max-width:0;opacity:0;overflow:hidden" height="1">
<%= preheader %>
</td>
</tr>
<tr>
<td align="center" class="mailpoet-wrapper" valign="top" style="border-collapse:collapse;background-color:#f0f0f0"><!--[if mso]>
<table align="center" border="0" cellspacing="0" cellpadding="0"
width="660">
<tr>
<td class="mailpoet_content-wrapper" align="center" valign="top" width="660">
<![endif]--><table class="mailpoet_content-wrapper" border="0" width="660" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;max-width:660px;width:100%;border-collapse:collapse;background-color:#ffffff">
<tbody>
<tr>
<td class="mailpoet_content" align="center" style="border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td style="padding-left:0;padding-right:0;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_cols-one" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_spacer" height="36" valign="top" style="border-collapse:collapse"></td>
</tr>
<tr>
<td class="mailpoet_image mailpoet_padded_bottom mailpoet_padded_side" align="center" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<img src="http://newsletters.mailpoet.com/wp-content/uploads/2018/10/new_logo_orange.png" width="80" alt="new_logo_orange" style="height:auto;max-width:100%;-ms-interpolation-mode:bicubic;border:0;display:block;outline:none;text-align:center" />
</td>
</tr>
<tr>
<td class="mailpoet_spacer" height="26" valign="top" style="border-collapse:collapse"></td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<h1 style="text-align:center;padding:0;font-style:normal;font-weight:normal;margin:0 0 12px;color:#111111;font-family:'Trebuchet MS','Lucida Grande','Lucida Sans Unicode','Lucida Sans',Tahoma,sans-serif;font-size:40px;line-height:64px">
<strong><%= __('Your stats are in!') %></strong>
</h1>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<h3 class="title" style="text-align:center;padding:0;font-style:normal;font-weight:normal;margin:0 0 6px;color:#333333;font-family:'Courier New',Courier,'Lucida Sans Typewriter','Lucida Typewriter',monospace;font-size:20px;line-height:32px">
<em><%= subject %></em>
</h3>
</td>
</tr>
<tr>
<td class="mailpoet_spacer" height="55" valign="top" style="border-collapse:collapse"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td class="mailpoet_content-cols-two" align="left" style="border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td align="center" style="font-size:0;border-collapse:collapse">
<!--[if mso]>
<table border="0" width="100%" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td width="330" valign="top">
<![endif]-->
<div style="display:inline-block; max-width:330px; vertical-align:top; width:100%;">
<table width="330" class="mailpoet_cols-two" border="0" cellpadding="0" cellspacing="0" align="left" style="width:100%;max-width:330px;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_button-container" style="text-align:center;border-collapse:collapse">
<a class="mailpoet_button" href="" style="display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none;text-align:center;background-color:#<%= openedColor %> ;border-color:#0074a2 ;border-width:0px ;border-radius:3px ;border-style:solid ;width:100px ;line-height:20px ;color:#ffffff ;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif ;font-size:10px ;font-weight:normal ">
<% if opened > 30 %>
<%= __('EXCELLENT') %>
<% elseif opened > 10 %>
<%= __('GOOD') %>
<% else %>
<%= __('BAD') %>
<% endif %>
</a></td>
</tr>
</table>
</div>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<h2 style="text-align:center;padding:0;font-style:normal;font-weight:normal;margin:0 0 12px;color:#222222;font-family:'Courier New',Courier,'Lucida Sans Typewriter','Lucida Typewriter',monospace;font-size:40px;line-height:64px">
<span style="color: #<%= openedColor %>;">
<strong><%= number_format_i18n(opened) %>%</strong>
</span>
</h2>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:center;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<span style="color: #<%= openedColor %>;">
<%= __('open rate') %>
</span>
</td>
</tr></table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso]>
</td>
<td width="330" valign="top">
<![endif]-->
<div style="display:inline-block; max-width:330px; vertical-align:top; width:100%;">
<table width="330" class="mailpoet_cols-two" border="0" cellpadding="0" cellspacing="0" align="left" style="width:100%;max-width:330px;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_button-container" style="text-align:center;border-collapse:collapse">
<a class="mailpoet_button" href="" style="display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none;text-align:center;background-color:#<%= clickedColor %> ;border-color:#0074a2 ;border-width:0px ;border-radius:3px ;border-style:solid ;width:100px ;line-height:20px ;color:#ffffff ;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif ;font-size:10px ;font-weight:normal ">
<% if clicked > 3 %>
<%= __('EXCELLENT') %>
<% elseif clicked > 1 %>
<%= __('GOOD') %>
<% else %>
<%= __('BAD') %>
<% endif %>
</a></td>
</tr>
</table>
</div>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<h2 style="text-align:center;padding:0;font-style:normal;font-weight:normal;margin:0 0 12px;color:#222222;font-family:'Courier New',Courier,'Lucida Sans Typewriter','Lucida Typewriter',monospace;font-size:40px;line-height:64px">
<span style="color: #<%= clickedColor %>;">
<strong><%= number_format_i18n(clicked) %>%</strong>
</span>
</h2>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:center;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<span style="color: #<%= clickedColor %>;">
<%= __('click rate') %>
</span>
</td>
</tr></table>
</td>
</tr>
</tbody>
</table>
</div><!--[if mso]>
</td>
</tr>
</tbody>
</table>
<![endif]--></td>
</tr>
</tbody>
</table>
</td>
</tr>
<% if topLinkClicks > 0 %>
<tr>
<td class="mailpoet_content" align="center" style="border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td style="padding-left:0;padding-right:0;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_cols-one" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_divider" valign="top" style="padding:26.5px 20px 26.5px 20px;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_divider-cell" style="border-top-width:1px;border-top-style:solid;border-top-color:#e8e8e8;border-collapse:collapse">
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td class="mailpoet_content" align="center" style="border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td style="padding-left:0;padding-right:0;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_cols-one" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_header_footer_padded mailpoet_header" style="line-height:38.4px;text-align:center ;color:#222222 ;font-family:'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif ;font-size:24px ;border-collapse:collapse;padding:10px 20px">
<span style="font-weight: 600;">
<%= __('Most clicked link') %>
</span>
</td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:center;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<% if topLink starts with 'http' %>
<a href="<%= topLink %>" target="_blank" rel="noopener noreferrer" style="color:#008282;text-decoration:underline">
<%= topLink %>
</a>
<% else %>
<%= topLink %>
<% endif %>
</td>
</tr></table>
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:center;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<span style="color: #000000;">
<%= __('%s unique clicks')|replace({'%s': topLinkClicks}) %>
</span>
</td>
</tr></table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<% endif %>
<tr>
<td class="mailpoet_content" align="center" style="border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td style="padding-left:0;padding-right:0;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_cols-one" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_divider" valign="top" style="padding:6.5px 20px 6.5px 20px;border-collapse:collapse">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_divider-cell" style="border-top-width:1px;border-top-style:solid;border-top-color:#e8e8e8;border-collapse:collapse">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="mailpoet_spacer" height="30" valign="top" style="border-collapse:collapse"></td>
</tr>
<% if premiumPluginActive %>
<tr>
<td class="mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_button-container" style="text-align:center;border-collapse:collapse">
<a class="mailpoet_button" href="<%= linkStats %>" style="display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none;text-align:center;background-color:#fe5301 ;border-color:#0074a2 ;border-width:0px ;border-radius:3px ;border-style:solid ;width:288px ;line-height:50px ;color:#ffffff ;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif ;font-size:20px ;font-weight:normal ">
<%= __('View all stats') %>
</a></td>
</tr>
</table>
</div>
</td>
</tr>
<% else %>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:left;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<%= __('See more stats in the Premium version, like all the links that were clicked or which subscribers opened your emails. You can also create segments of subscribers by clicks and opens.') %>
</td>
</tr></table>
</td>
</tr>
<tr>
<td class="mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tr>
<td class="mailpoet_button-container" style="text-align:center;border-collapse:collapse">
<a class="mailpoet_button" href="<%= premiumPage %>" style="display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none;text-align:center;background-color:#fe5301 ;border-color:#0074a2 ;border-width:0px ;border-radius:3px ;border-style:solid ;width:288px ;line-height:50px ;color:#ffffff ;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif ;font-size:20px ;font-weight:normal ">
<%= __('See Premium features') %>
</a></td>
</tr>
</table>
</div>
</td>
</tr>
<% endif %>
<tr>
<td class="mailpoet_spacer" height="20" valign="top" style="border-collapse:collapse"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td class="mailpoet_content-cols-two" align="left" style="border-collapse:collapse;background-color:#fe5301" bgcolor="#fe5301">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse">
<tbody>
<tr>
<td align="center" style="font-size:0;border-collapse:collapse"><!--[if mso]>
<table border="0" width="100%" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td width="330" valign="top">
<![endif]--><div style="display:inline-block; max-width:330px; vertical-align:top; width:100%;">
<table width="330" class="mailpoet_cols-two" border="0" cellpadding="0" cellspacing="0" align="left" style="width:100%;max-width:330px;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_spacer" bgcolor="#fe5301" height="24" valign="top" style="border-collapse:collapse"></td>
</tr>
<tr>
<td class="mailpoet_image mailpoet_padded_bottom mailpoet_padded_side" align="left" valign="top" style="border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<img src="http://newsletters.mailpoet.com/wp-content/uploads/2018/10/new_logo_white-300x95.png" width="130" alt="new_logo_white" style="height:auto;max-width:100%;-ms-interpolation-mode:bicubic;border:0;display:block;outline:none;text-align:center" />
</td>
</tr>
</tbody>
</table>
</div><!--[if mso]>
</td>
<td width="330" valign="top">
<![endif]--><div style="display:inline-block; max-width:330px; vertical-align:top; width:100%;">
<table width="330" class="mailpoet_cols-two" border="0" cellpadding="0" cellspacing="0" align="left" style="width:100%;max-width:330px;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;border-collapse:collapse">
<tbody>
<tr>
<td class="mailpoet_spacer" bgcolor="#fe5301" height="20" valign="top" style="border-collapse:collapse"></td>
</tr>
<tr>
<td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;padding-top:0;border-collapse:collapse;padding-bottom:20px;padding-left:20px;padding-right:20px">
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:right;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<span style="color: #ffffff;">
<a href="https://mailpoet.com/how-to-improve-open-rates" title="<%= __('How to Improve Open and Click Rates') %>" style="color:#008282;text-decoration:underline">
<%= __('How to improve my open rate?') %>
</a>
</span>
</td>
</tr></table>
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:right;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<span style="color: #ffffff;"><a href="https://mailpoet.com/how-to-improve-click-rates" title="<%= __('How to Improve Open and Click Rates') %>" style="color:#008282;text-decoration:underline">
<%= __('And my click rate?') %>
</a></span>
</td>
</tr></table>
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;border-collapse:collapse" width="100%" cellpadding="0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;text-align:right;border-collapse:collapse;color:#000000;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:16px;line-height:25.6px">
<a href="<%= linkSettings %>" style="color:#008282;text-decoration:underline">
<%= __('Disable these emails') %>
</a>
</td>
</tr></table>
</td>
</tr>
</tbody>
</table>
</div><!--[if mso]>
</td>
</tr>
</tbody>
</table>
<![endif]--></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table><!--[if mso]>
</td>
</tr>
</table>
<![endif]--></td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,40 @@
<%= __('Your stats are in!') %>
<%= subject %>
<%= __('open rate') %>: <%= number_format_i18n(opened) %>%
<% if opened > 30 %>
<%= __('EXCELLENT') %>
<% elseif opened > 10 %>
<%= __('GOOD') %>
<% else %>
<%= __('BAD') %>
<% endif %>
<%= __('click rate') %>: <%= number_format_i18n(clicked) %>%
<% if clicked > 3 %>
<%= __('EXCELLENT') %>
<% elseif clicked > 1 %>
<%= __('GOOD') %>
<% else %>
<%= __('BAD') %>
<% endif %>
<% if topLinkClicks > 0 %>
<%= __('Most clicked link') %>
<%= topLink %>
<%= __('%s unique clicks')|replace({'%s': topLinkClicks}) %>
<% endif %>
<% if premiumPluginActive %>
<%= __('View all stats') %>
<%= linkStats %>
<% else %>
<%= __('See Premium features') %>
<%= premiumPage %>
<% endif %>
<%= __('How to improve my open rate?') %> https://mailpoet.com/how-to-improve-open-rates
<%= __('And my click rate?') %> https://mailpoet.com/how-to-improve-click-rates
<%= __('Disable these emails') %> <%= linkSettings %>

View File

@ -98,6 +98,15 @@
} else { } else {
$('#settings_subscriber_email_notification_error').hide(); $('#settings_subscriber_email_notification_error').hide();
} }
var stats_notifications_enabled = $('input[name="stats_notifications[enabled]"]:checked').val(),
stats_notifications_address = $('input[name="stats_notifications[address]"]').val().trim();
if (stats_notifications_enabled && stats_notifications_address == '') {
$('#settings_stats_notifications_error').show();
window.location.href = '#basics';
errorFound = true;
} else {
$('#settings_stats_notifications_error').hide();
}
// stop processing if an error was found // stop processing if an error was found
if (errorFound) { if (errorFound) {
return false; return false;
@ -182,6 +191,7 @@
$('#settings_re_captcha_tokens_error').hide(); $('#settings_re_captcha_tokens_error').hide();
$('#settings_subscriber_email_notification_error').hide(); $('#settings_subscriber_email_notification_error').hide();
$('#settings_stats_notifications_error').hide();
function toggleLinuxCronSettings() { function toggleLinuxCronSettings() {
if ($('input[name="cron_trigger[method]"]:checked').val() === '<%= cron_trigger.linux_cron %>') { if ($('input[name="cron_trigger[method]"]:checked').val() === '<%= cron_trigger.linux_cron %>') {

View File

@ -281,6 +281,48 @@
</td> </td>
</tr> </tr>
<!-- Stats notifications -->
<tr>
<th scope="row">
<label for="subscription_unsubscribe_page">
<%= _x('Stats notifications', 'name of a setting to automatically send statistics (newsletter open rate, click rate, etc) by email') %>
</label>
<p class="description">
<%= __('Enter the email address that should receive your newsletters stats 24 hours after it has been sent.') %>
<td>
<p>
<label>
<input
type="radio"
name="stats_notifications[enabled]"
value="1"
<% if(settings.stats_notifications.enabled) %>
checked
<% endif %>
/><%= __('Yes') %>
</label>
&nbsp;
<label>
<input
type="radio"
name="stats_notifications[enabled]"
value=""
<% if not(settings.stats_notifications.enabled) %>
checked
<% endif %>
><%= __('No') %>
</label>
<br>
<input type="email"
id="stats_notifications[address]"
name="stats_notifications[address]"
value="<%= settings.stats_notifications.address %>"
placeholder="me@mydomain.com" >
<br>
<div id="settings_stats_notifications_error" class="mailpoet_error_item mailpoet_error">
<%= __('Please fill the email address.') %>
</div>
<!-- New subscriber emails notifications --> <!-- New subscriber emails notifications -->
<tr> <tr>
<th scope="row"> <th scope="row">
@ -296,9 +338,9 @@
type="radio" type="radio"
name="subscriber_email_notification[enabled]" name="subscriber_email_notification[enabled]"
value="1" value="1"
<% if(settings.subscriber_email_notification.enabled) %> <% if(settings.subscriber_email_notification.enabled) %>
checked checked
<% endif %> <% endif %>
/><%= __('Yes') %> /><%= __('Yes') %>
</label> </label>
&nbsp; &nbsp;
@ -307,21 +349,22 @@
type="radio" type="radio"
name="subscriber_email_notification[enabled]" name="subscriber_email_notification[enabled]"
value="" value=""
<% if not(settings.subscriber_email_notification.enabled) %> <% if not(settings.subscriber_email_notification.enabled) %>
checked checked
<% endif %> <% endif %>
/><%= __('No') %> /><%= __('No') %>
</label> </label>
<br> <br>
<input type="email" <input type="email"
id="subscriber_email_notification[address]" id="subscriber_email_notification[address]"
name="subscriber_email_notification[address]" name="subscriber_email_notification[address]"
value="<%= settings.subscriber_email_notification.address %>" value="<%= settings.subscriber_email_notification.address %>"
placeholder="me@mydomain.com" /> placeholder="me@mydomain.com" />
<br> <br>
<div id="settings_subscriber_email_notification_error" class="mailpoet_error_item mailpoet_error"> <div id="settings_subscriber_email_notification_error" class="mailpoet_error_item mailpoet_error">
<%= __('Please fill the email address.') %> <%= __('Please fill the email address.') %>
</div> </div>
<!-- shortcode: archive page --> <!-- shortcode: archive page -->
<tr> <tr>
<th scope="row"> <th scope="row">