Use Newsletter Link Entity in Stats Notifications
[MAILPOET-2439]
This commit is contained in:
committed by
Jack Kitterhing
parent
4c960a1a44
commit
ad6e6009d2
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
|
use MailPoet\Doctrine\Repository;
|
||||||
|
use MailPoet\Entities\NewsletterLinkEntity;
|
||||||
|
|
||||||
|
class NewsletterLinkRepository extends Repository {
|
||||||
|
protected function getEntityClassName() {
|
||||||
|
return NewsletterLinkEntity::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $newsletter_id
|
||||||
|
* @return NewsletterLinkEntity
|
||||||
|
* @throws \MailPoetVendor\Doctrine\ORM\NoResultException
|
||||||
|
* @throws \MailPoetVendor\Doctrine\ORM\NonUniqueResultException
|
||||||
|
*/
|
||||||
|
public function findTopLinkForNewsletter($newsletter_id) {
|
||||||
|
return $this->doctrine_repository
|
||||||
|
->createQueryBuilder('nlr')
|
||||||
|
->join('nlr.clicks', 'c')
|
||||||
|
->addSelect('COUNT(c.id) AS HIDDEN counter')
|
||||||
|
->where('nlr.newsletter_id = :newsletterId')
|
||||||
|
->setParameter('newsletterId', $newsletter_id)
|
||||||
|
->groupBy('nlr.id')
|
||||||
|
->orderBy('counter', 'desc')
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getQuery()
|
||||||
|
->getSingleResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,7 @@ namespace MailPoet\Cron\Workers\StatsNotifications;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use MailPoet\Config\Renderer;
|
use MailPoet\Config\Renderer;
|
||||||
use MailPoet\Cron\CronHelper;
|
use MailPoet\Cron\CronHelper;
|
||||||
|
use MailPoet\Entities\NewsletterLinkEntity;
|
||||||
use MailPoet\Entities\ScheduledTaskEntity;
|
use MailPoet\Entities\ScheduledTaskEntity;
|
||||||
use MailPoet\Entities\StatsNotificationEntity;
|
use MailPoet\Entities\StatsNotificationEntity;
|
||||||
use MailPoet\Mailer\Mailer;
|
use MailPoet\Mailer\Mailer;
|
||||||
@ -42,12 +43,16 @@ class Worker {
|
|||||||
/** @var EntityManager */
|
/** @var EntityManager */
|
||||||
private $entity_manager;
|
private $entity_manager;
|
||||||
|
|
||||||
|
/** @var NewsletterLinkRepository */
|
||||||
|
private $newsletter_link_repository;
|
||||||
|
|
||||||
function __construct(
|
function __construct(
|
||||||
Mailer $mailer,
|
Mailer $mailer,
|
||||||
Renderer $renderer,
|
Renderer $renderer,
|
||||||
SettingsController $settings,
|
SettingsController $settings,
|
||||||
MetaInfo $mailerMetaInfo,
|
MetaInfo $mailerMetaInfo,
|
||||||
StatsNotificationsRepository $repository,
|
StatsNotificationsRepository $repository,
|
||||||
|
NewsletterLinkRepository $newsletter_link_repository,
|
||||||
EntityManager $entity_manager,
|
EntityManager $entity_manager,
|
||||||
$timer = false
|
$timer = false
|
||||||
) {
|
) {
|
||||||
@ -58,6 +63,7 @@ class Worker {
|
|||||||
$this->mailerMetaInfo = $mailerMetaInfo;
|
$this->mailerMetaInfo = $mailerMetaInfo;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->entity_manager = $entity_manager;
|
$this->entity_manager = $entity_manager;
|
||||||
|
$this->newsletter_link_repository = $newsletter_link_repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @throws \Exception */
|
/** @throws \Exception */
|
||||||
@ -89,7 +95,11 @@ class Worker {
|
|||||||
|
|
||||||
private function constructNewsletter(StatsNotificationEntity $stats_notification_entity) {
|
private function constructNewsletter(StatsNotificationEntity $stats_notification_entity) {
|
||||||
$newsletter = $this->getNewsletter($stats_notification_entity);
|
$newsletter = $this->getNewsletter($stats_notification_entity);
|
||||||
$link = NewsletterLink::findTopLinkForNewsletter($newsletter);
|
try {
|
||||||
|
$link = $this->newsletter_link_repository->findTopLinkForNewsletter($newsletter->id);
|
||||||
|
} catch (\MailPoetVendor\Doctrine\ORM\UnexpectedResultException $e) {
|
||||||
|
$link = null;
|
||||||
|
}
|
||||||
$context = $this->prepareContext($newsletter, $link);
|
$context = $this->prepareContext($newsletter, $link);
|
||||||
$subject = $newsletter->queue['newsletter_rendered_subject'];
|
$subject = $newsletter->queue['newsletter_rendered_subject'];
|
||||||
return [
|
return [
|
||||||
@ -112,10 +122,10 @@ class Worker {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Newsletter $newsletter
|
* @param Newsletter $newsletter
|
||||||
* @param \stdClass|NewsletterLink $link
|
* @param NewsletterLinkEntity $link
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function prepareContext(Newsletter $newsletter, $link = null) {
|
private function prepareContext(Newsletter $newsletter, NewsletterLinkEntity $link = null) {
|
||||||
$clicked = ($newsletter->statistics['clicked'] * 100) / $newsletter->total_sent;
|
$clicked = ($newsletter->statistics['clicked'] * 100) / $newsletter->total_sent;
|
||||||
$opened = ($newsletter->statistics['opened'] * 100) / $newsletter->total_sent;
|
$opened = ($newsletter->statistics['opened'] * 100) / $newsletter->total_sent;
|
||||||
$unsubscribed = ($newsletter->statistics['unsubscribed'] * 100) / $newsletter->total_sent;
|
$unsubscribed = ($newsletter->statistics['unsubscribed'] * 100) / $newsletter->total_sent;
|
||||||
@ -135,9 +145,9 @@ class Worker {
|
|||||||
'opened' => $opened,
|
'opened' => $opened,
|
||||||
];
|
];
|
||||||
if ($link) {
|
if ($link) {
|
||||||
$context['topLinkClicks'] = (int)$link->clicksCount;
|
$context['topLinkClicks'] = $link->getTotalClicksCount();
|
||||||
$mappings = self::getShortcodeLinksMapping();
|
$mappings = self::getShortcodeLinksMapping();
|
||||||
$context['topLink'] = isset($mappings[$link->url]) ? $mappings[$link->url] : $link->url;
|
$context['topLink'] = isset($mappings[$link->getUrl()]) ? $mappings[$link->getUrl()] : $link->getUrl();
|
||||||
}
|
}
|
||||||
return $context;
|
return $context;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler::class)->setPublic(true);
|
$container->autowire(\MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\Scheduler::class);
|
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\Scheduler::class);
|
||||||
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\StatsNotificationsRepository::class);
|
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\StatsNotificationsRepository::class);
|
||||||
|
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository::class);
|
||||||
$container->autowire(\MailPoet\Cron\CronTrigger::class)->setPublic(true);
|
$container->autowire(\MailPoet\Cron\CronTrigger::class)->setPublic(true);
|
||||||
// Custom field
|
// Custom field
|
||||||
$container->autowire(\MailPoet\CustomFields\ApiDataSanitizer::class);
|
$container->autowire(\MailPoet\CustomFields\ApiDataSanitizer::class);
|
||||||
|
97
lib/Entities/NewsletterLinkEntity.php
Normal file
97
lib/Entities/NewsletterLinkEntity.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Entities;
|
||||||
|
|
||||||
|
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
|
||||||
|
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
|
||||||
|
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
|
||||||
|
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
|
||||||
|
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity()
|
||||||
|
* @ORM\Table(name="newsletter_links")
|
||||||
|
*/
|
||||||
|
class NewsletterLinkEntity {
|
||||||
|
use AutoincrementedIdTrait;
|
||||||
|
use CreatedAtTrait;
|
||||||
|
use UpdatedAtTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $newsletter_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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="string")
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string")
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $hash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra lazy is here for `getTotalClicksCount`.
|
||||||
|
* If we didn't specify extra lazy the function would load all clicks and count them. This way it uses a single count query.
|
||||||
|
* @ORM\OneToMany(targetEntity="MailPoet\Entities\StatisticsClicksEntity", mappedBy="link", fetch="EXTRA_LAZY")
|
||||||
|
* @var StatisticsClicksEntity[]|ArrayCollection
|
||||||
|
*/
|
||||||
|
private $clicks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return NewsletterEntity|null
|
||||||
|
*/
|
||||||
|
public function getNewsletter() {
|
||||||
|
return $this->newsletter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SendingQueueEntity|null
|
||||||
|
*/
|
||||||
|
public function getQueue() {
|
||||||
|
return $this->queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getUrl() {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getHash() {
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getTotalClicksCount() {
|
||||||
|
$criteria = Criteria::create()
|
||||||
|
->where(Criteria::expr()->eq("link", $this));
|
||||||
|
return $this->clicks->matching($criteria)->count();
|
||||||
|
}
|
||||||
|
}
|
51
lib/Entities/StatisticsClicksEntity.php
Normal file
51
lib/Entities/StatisticsClicksEntity.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?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_clicks")
|
||||||
|
*/
|
||||||
|
class StatisticsClicksEntity {
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterLinkEntity", inversedBy="clicks")
|
||||||
|
* @var NewsletterLinkEntity
|
||||||
|
*/
|
||||||
|
private $link;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
private $count;
|
||||||
|
|
||||||
|
}
|
@ -12,27 +12,4 @@ namespace MailPoet\Models;
|
|||||||
class NewsletterLink extends Model {
|
class NewsletterLink extends Model {
|
||||||
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
|
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
|
||||||
const UNSUBSCRIBE_LINK_SHORT_CODE = '[link:subscription_unsubscribe_url]';
|
const UNSUBSCRIBE_LINK_SHORT_CODE = '[link:subscription_unsubscribe_url]';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Newsletter $newsletter
|
|
||||||
* @return NewsletterLink|null
|
|
||||||
*/
|
|
||||||
static function findTopLinkForNewsletter(Newsletter $newsletter) {
|
|
||||||
$link = self::selectExpr('links.*')
|
|
||||||
->selectExpr('count(*)', 'clicksCount')
|
|
||||||
->tableAlias('links')
|
|
||||||
->innerJoin(StatisticsClicks::$_table,
|
|
||||||
['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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ use MailPoet\Models\StatisticsUnsubscribes;
|
|||||||
use MailPoet\Models\StatsNotification;
|
use MailPoet\Models\StatsNotification;
|
||||||
use MailPoet\Settings\SettingsController;
|
use MailPoet\Settings\SettingsController;
|
||||||
use MailPoet\WooCommerce\Helper as WCHelper;
|
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
|
||||||
class WorkerTest extends \MailPoetTest {
|
class WorkerTest extends \MailPoetTest {
|
||||||
@ -43,12 +42,16 @@ class WorkerTest extends \MailPoetTest {
|
|||||||
/** @var StatsNotificationsRepository */
|
/** @var StatsNotificationsRepository */
|
||||||
private $repository;
|
private $repository;
|
||||||
|
|
||||||
|
/** @var NewsletterLinkRepository */
|
||||||
|
private $newsletter_link_repository;
|
||||||
|
|
||||||
function _before() {
|
function _before() {
|
||||||
parent::_before();
|
parent::_before();
|
||||||
\ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
|
\ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
|
||||||
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
|
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
|
||||||
\ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
\ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||||
$this->repository = ContainerWrapper::getInstance()->get(StatsNotificationsRepository::class);
|
$this->repository = ContainerWrapper::getInstance()->get(StatsNotificationsRepository::class);
|
||||||
|
$this->newsletter_link_repository = ContainerWrapper::getInstance()->get(NewsletterLinkRepository::class);
|
||||||
$this->repository->truncate();
|
$this->repository->truncate();
|
||||||
$this->mailer = $this->createMock(Mailer::class);
|
$this->mailer = $this->createMock(Mailer::class);
|
||||||
$this->renderer = $this->createMock(Renderer::class);
|
$this->renderer = $this->createMock(Renderer::class);
|
||||||
@ -59,6 +62,7 @@ class WorkerTest extends \MailPoetTest {
|
|||||||
$this->settings,
|
$this->settings,
|
||||||
new MetaInfo,
|
new MetaInfo,
|
||||||
$this->repository,
|
$this->repository,
|
||||||
|
$this->newsletter_link_repository,
|
||||||
$this->entity_manager
|
$this->entity_manager
|
||||||
);
|
);
|
||||||
$this->settings->set(Worker::SETTINGS_KEY, [
|
$this->settings->set(Worker::SETTINGS_KEY, [
|
||||||
|
Reference in New Issue
Block a user