Add newsletter statistics to doctrine

[MAILPOET-2439]
This commit is contained in:
Pavel Dohnal
2019-10-28 11:18:17 +01:00
committed by Jack Kitterhing
parent ad6e6009d2
commit 5a2edff9fe
8 changed files with 257 additions and 24 deletions

View File

@ -5,6 +5,7 @@ namespace MailPoet\Cron\Workers\StatsNotifications;
use Carbon\Carbon;
use MailPoet\Config\Renderer;
use MailPoet\Cron\CronHelper;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterLinkEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\StatsNotificationEntity;
@ -12,6 +13,7 @@ use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\MetaInfo;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\ScheduledTask;
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
use MailPoet\Settings\SettingsController;
use MailPoet\Tasks\Sending;
use MailPoet\WP\Functions as WPFunctions;
@ -46,6 +48,9 @@ class Worker {
/** @var NewsletterLinkRepository */
private $newsletter_link_repository;
/** @var NewsletterStatisticsRepository */
private $newsletter_statistics_repository;
function __construct(
Mailer $mailer,
Renderer $renderer,
@ -53,6 +58,7 @@ class Worker {
MetaInfo $mailerMetaInfo,
StatsNotificationsRepository $repository,
NewsletterLinkRepository $newsletter_link_repository,
NewsletterStatisticsRepository $newsletter_statistics_repository,
EntityManager $entity_manager,
$timer = false
) {
@ -64,6 +70,7 @@ class Worker {
$this->repository = $repository;
$this->entity_manager = $entity_manager;
$this->newsletter_link_repository = $newsletter_link_repository;
$this->newsletter_statistics_repository = $newsletter_statistics_repository;
}
/** @throws \Exception */
@ -94,14 +101,14 @@ class Worker {
}
private function constructNewsletter(StatsNotificationEntity $stats_notification_entity) {
$newsletter = $this->getNewsletter($stats_notification_entity);
$newsletter = $stats_notification_entity->getNewsletter();
try {
$link = $this->newsletter_link_repository->findTopLinkForNewsletter($newsletter->id);
$link = $this->newsletter_link_repository->findTopLinkForNewsletter($newsletter->getId());
} catch (\MailPoetVendor\Doctrine\ORM\UnexpectedResultException $e) {
$link = null;
}
$context = $this->prepareContext($newsletter, $link);
$subject = $newsletter->queue['newsletter_rendered_subject'];
$subject = $newsletter->getLatestQueue()->getNewsletterRenderedSubject();
return [
'subject' => sprintf(_x('Stats for email %s', 'title of an automatic email containing statistics (newsletter open rate, click rate, etc)', 'mailpoet'), $subject),
'body' => [
@ -111,25 +118,12 @@ class Worker {
];
}
private function getNewsletter(StatsNotificationEntity $stats_notification_entity) {
$newsletter = $stats_notification_entity->getNewsletter();
$newsletter = Newsletter::findOne($newsletter->getId());
return $newsletter
->withSendingQueue()
->withTotalSent()
->withStatistics($this->woocommerce_helper);
}
/**
* @param Newsletter $newsletter
* @param NewsletterLinkEntity $link
* @return array
*/
private function prepareContext(Newsletter $newsletter, NewsletterLinkEntity $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;
$subject = $newsletter->queue['newsletter_rendered_subject'];
private function prepareContext(NewsletterEntity $newsletter, NewsletterLinkEntity $link = null) {
$statistics = $this->newsletter_statistics_repository->getStatistics($newsletter);
$clicked = ($statistics->getClickCount() * 100) / $statistics->getTotalSentCount();
$opened = ($statistics->getOpenCount() * 100) / $statistics->getTotalSentCount();
$unsubscribed = ($statistics->getUnsubscribeCount() * 100) / $statistics->getTotalSentCount();
$subject = $newsletter->getLatestQueue()->getNewsletterRenderedSubject();
$context = [
'subject' => $subject,
'preheader' => sprintf(_x(
@ -140,7 +134,7 @@ class Worker {
),
'topLinkClicks' => 0,
'linkSettings' => WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-settings#basics'),
'linkStats' => WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-newsletters#/stats/' . $newsletter->id()),
'linkStats' => WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-newsletters#/stats/' . $newsletter->getId()),
'clicked' => $clicked,
'opened' => $opened,
];

View File

@ -11,6 +11,7 @@ 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\StatsNotifications\AutomatedEmails as StatsNotificationsWorkerForAutomatedEmails;
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationScheduler;
use MailPoet\Cron\Workers\StatsNotifications\StatsNotificationsRepository;
use MailPoet\Cron\Workers\StatsNotifications\Worker as StatsNotificationsWorker;
@ -19,6 +20,7 @@ use MailPoet\Logging\LoggerFactory;
use MailPoet\Mailer\Mailer;
use MailPoet\Mailer\MetaInfo;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
use MailPoet\Segments\SubscribersFinder;
use MailPoet\Segments\WooCommerce as WooCommerceSegment;
use MailPoet\Services\AuthorizedEmailsController;
@ -81,6 +83,12 @@ class WorkersFactory {
*/
private $newsletters_repository;
/** @var NewsletterLinkRepository */
private $newsletter_link_repository;
/** @var NewsletterStatisticsRepository */
private $newsletter_statistics_repository;
public function __construct(
SendingErrorHandler $sending_error_handler,
StatsNotificationScheduler $statsNotificationsScheduler,
@ -97,6 +105,8 @@ class WorkersFactory {
LoggerFactory $logger_factory,
StatsNotificationsRepository $stats_notifications_repository,
NewslettersRepository $newsletters_repository,
NewsletterLinkRepository $newsletter_link_repository,
NewsletterStatisticsRepository $newsletter_statistics_repository,
EntityManager $entity_manager
) {
$this->sending_error_handler = $sending_error_handler;
@ -115,6 +125,8 @@ class WorkersFactory {
$this->stats_notifications_repository = $stats_notifications_repository;
$this->entity_manager = $entity_manager;
$this->newsletters_repository = $newsletters_repository;
$this->newsletter_link_repository = $newsletter_link_repository;
$this->newsletter_statistics_repository = $newsletter_statistics_repository;
}
/** @return SchedulerWorker */
@ -141,6 +153,8 @@ class WorkersFactory {
$this->settings,
$this->mailerMetaInfo,
$this->stats_notifications_repository,
$this->newsletter_link_repository,
$this->newsletter_statistics_repository,
$this->entity_manager,
$timer
);

View File

@ -203,6 +203,7 @@ class ContainerConfigurator implements IContainerConfigurator {
// Newsletter
$container->autowire(\MailPoet\Newsletter\AutomatedLatestContent::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\NewslettersRepository::class);
$container->autowire(\MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository::class);
$container->autowire(\MailPoet\Newsletter\Scheduler\WelcomeScheduler::class);
$container->autowire(\MailPoet\Newsletter\Scheduler\PostNotificationScheduler::class);
// Util

View File

@ -0,0 +1,39 @@
<?php
namespace MailPoet\Entities;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_opens")
*/
class StatisticsOpensEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\Column(type="integer")
* @var int|null
*/
private $subscriber_id;
}

View File

@ -0,0 +1,39 @@
<?php
namespace MailPoet\Entities;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_unsubscribes")
*/
class StatisticsUnsubscribesEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\Column(type="integer")
* @var int|null
*/
private $subscriber_id;
}

View File

@ -0,0 +1,54 @@
<?php
namespace MailPoet\Newsletter\Statistics;
class NewsletterStatistics {
/** @var int */
private $clickCount;
/** @var int */
private $openCount;
/** @var int */
private $unsubscribeCount;
/** @var int */
private $total_sent_count;
function __construct($clickCount, $openCount, $unsubscribeCount, $total_sent_count) {
$this->clickCount = $clickCount;
$this->openCount = $openCount;
$this->unsubscribeCount = $unsubscribeCount;
$this->total_sent_count = $total_sent_count;
}
/**
* @return int
*/
public function getClickCount() {
return $this->clickCount;
}
/**
* @return int
*/
public function getOpenCount() {
return $this->openCount;
}
/**
* @return int
*/
public function getUnsubscribeCount() {
return $this->unsubscribeCount;
}
/**
* @return int
*/
public function getTotalSentCount() {
return $this->total_sent_count;
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace MailPoet\Newsletter\Statistics;
use MailPoet\Doctrine\Repository;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\StatisticsClicksEntity;
use MailPoet\Entities\StatisticsOpensEntity;
use MailPoet\Entities\StatisticsUnsubscribesEntity;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\ORM\UnexpectedResultException as UnexpectedResultExceptionAlias;
class NewsletterStatisticsRepository extends Repository {
protected function getEntityClassName() {
return NewsletterEntity::class;
}
/**
* @param NewsletterEntity $newsletter
* @return int
*/
function getTotalSentCount(NewsletterEntity $newsletter) {
try {
return (int)$this->doctrine_repository
->createQueryBuilder('n')
->join('n.queues', 'q')
->join('q.task', 't')
->select('SUM(q.count_processed)')
->where('t.status = :status')
->setParameter('status', ScheduledTaskEntity::STATUS_COMPLETED)
->getQuery()
->getSingleScalarResult();
} catch (UnexpectedResultExceptionAlias $e) {
return 0;
}
}
/**
* @param NewsletterEntity $newsletter
* @return NewsletterStatistics
*/
function getStatistics(NewsletterEntity $newsletter) {
return new NewsletterStatistics(
$this->getStatisticsClickCount($newsletter),
$this->getStatisticsOpenCount($newsletter),
$this->getStatisticsUnsubscribeCount($newsletter),
$this->getTotalSentCount($newsletter)
);
}
/**
* @param NewsletterEntity $newsletter
* @return int
*/
function getStatisticsClickCount(NewsletterEntity $newsletter) {
return $this->getStatisticsCount($newsletter, StatisticsClicksEntity::class);
}
/**
* @param NewsletterEntity $newsletter
* @return int
*/
function getStatisticsOpenCount(NewsletterEntity $newsletter) {
return $this->getStatisticsCount($newsletter, StatisticsOpensEntity::class);
}
/**
* @param NewsletterEntity $newsletter
* @return int
*/
function getStatisticsUnsubscribeCount(NewsletterEntity $newsletter) {
return $this->getStatisticsCount($newsletter, StatisticsUnsubscribesEntity::class);
}
private function getStatisticsCount(NewsletterEntity $newsletter, $statistics_entity_name) {
try {
$qb = $this->entity_manager
->createQueryBuilder();
return $qb->select('COUNT(DISTINCT stats.subscriber_id) as cnt')
->from($statistics_entity_name, 'stats')
->where('stats.newsletter = :newsletter')
->setParameter('newsletter', $newsletter)
->getQuery()
->getSingleScalarResult();
} catch (UnexpectedResultExceptionAlias $e) {
return 0;
}
}
}

View File

@ -14,7 +14,7 @@ use MailPoet\Models\SendingQueue;
use MailPoet\Models\StatisticsClicks;
use MailPoet\Models\StatisticsOpens;
use MailPoet\Models\StatisticsUnsubscribes;
use MailPoet\Models\StatsNotification;
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
use MailPoet\Settings\SettingsController;
use MailPoet\WooCommerce\Helper as WCHelper;
use PHPUnit\Framework\MockObject\MockObject;
@ -48,6 +48,7 @@ class WorkerTest extends \MailPoetTest {
function _before() {
parent::_before();
\ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
\ORM::raw_execute('TRUNCATE ' . StatisticsClicks::$_table);
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
\ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
$this->repository = ContainerWrapper::getInstance()->get(StatsNotificationsRepository::class);
@ -63,6 +64,7 @@ class WorkerTest extends \MailPoetTest {
new MetaInfo,
$this->repository,
$this->newsletter_link_repository,
ContainerWrapper::getInstance()->get(NewsletterStatisticsRepository::class),
$this->entity_manager
);
$this->settings->set(Worker::SETTINGS_KEY, [