Use Newsletter Link Entity in Stats Notifications

[MAILPOET-2439]
This commit is contained in:
Pavel Dohnal
2019-10-28 09:37:01 +01:00
committed by Jack Kitterhing
parent 4c960a1a44
commit ad6e6009d2
7 changed files with 202 additions and 29 deletions

View File

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

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\NewsletterLinkEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\StatsNotificationEntity;
use MailPoet\Mailer\Mailer;
@ -42,12 +43,16 @@ class Worker {
/** @var EntityManager */
private $entity_manager;
/** @var NewsletterLinkRepository */
private $newsletter_link_repository;
function __construct(
Mailer $mailer,
Renderer $renderer,
SettingsController $settings,
MetaInfo $mailerMetaInfo,
StatsNotificationsRepository $repository,
NewsletterLinkRepository $newsletter_link_repository,
EntityManager $entity_manager,
$timer = false
) {
@ -58,6 +63,7 @@ class Worker {
$this->mailerMetaInfo = $mailerMetaInfo;
$this->repository = $repository;
$this->entity_manager = $entity_manager;
$this->newsletter_link_repository = $newsletter_link_repository;
}
/** @throws \Exception */
@ -89,7 +95,11 @@ class Worker {
private function constructNewsletter(StatsNotificationEntity $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);
$subject = $newsletter->queue['newsletter_rendered_subject'];
return [
@ -112,10 +122,10 @@ class Worker {
/**
* @param Newsletter $newsletter
* @param \stdClass|NewsletterLink $link
* @param NewsletterLinkEntity $link
* @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;
$opened = ($newsletter->statistics['opened'] * 100) / $newsletter->total_sent;
$unsubscribed = ($newsletter->statistics['unsubscribed'] * 100) / $newsletter->total_sent;
@ -135,9 +145,9 @@ class Worker {
'opened' => $opened,
];
if ($link) {
$context['topLinkClicks'] = (int)$link->clicksCount;
$context['topLinkClicks'] = $link->getTotalClicksCount();
$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;
}

View File

@ -131,6 +131,7 @@ class ContainerConfigurator implements IContainerConfigurator {
$container->autowire(\MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler::class)->setPublic(true);
$container->autowire(\MailPoet\Cron\Workers\StatsNotifications\Scheduler::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);
// Custom field
$container->autowire(\MailPoet\CustomFields\ApiDataSanitizer::class);

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

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

View File

@ -12,27 +12,4 @@ namespace MailPoet\Models;
class NewsletterLink extends Model {
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
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;
}
}

View File

@ -17,7 +17,6 @@ use MailPoet\Models\StatisticsUnsubscribes;
use MailPoet\Models\StatsNotification;
use MailPoet\Settings\SettingsController;
use MailPoet\WooCommerce\Helper as WCHelper;
use MailPoetVendor\Doctrine\ORM\EntityManager;
use PHPUnit\Framework\MockObject\MockObject;
class WorkerTest extends \MailPoetTest {
@ -43,12 +42,16 @@ class WorkerTest extends \MailPoetTest {
/** @var StatsNotificationsRepository */
private $repository;
/** @var NewsletterLinkRepository */
private $newsletter_link_repository;
function _before() {
parent::_before();
\ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
\ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
$this->repository = ContainerWrapper::getInstance()->get(StatsNotificationsRepository::class);
$this->newsletter_link_repository = ContainerWrapper::getInstance()->get(NewsletterLinkRepository::class);
$this->repository->truncate();
$this->mailer = $this->createMock(Mailer::class);
$this->renderer = $this->createMock(Renderer::class);
@ -59,6 +62,7 @@ class WorkerTest extends \MailPoetTest {
$this->settings,
new MetaInfo,
$this->repository,
$this->newsletter_link_repository,
$this->entity_manager
);
$this->settings->set(Worker::SETTINGS_KEY, [