Use Doctrine for the opens and clicks exporters

This commit refactors the code that handles exporting e-mails opens and
clicks when generating the personal data file to use Doctrine instead of
Paris. I opted to do this in this task as opens and clicks code share
some functionality, and I didn't want to add more code that relies on
Paris, as we are eventually going to remove it.

[MAILPOET-3738]
This commit is contained in:
Rodrigo Primo
2021-08-19 16:54:06 -03:00
committed by Veljko V
parent 780ac5e53c
commit 8b7815caf8
11 changed files with 68 additions and 88 deletions

View File

@@ -47,7 +47,7 @@ class PersonalDataExporters {
public function registerNewsletterClicksExporter($exporters) { public function registerNewsletterClicksExporter($exporters) {
$exporters[] = [ $exporters[] = [
'exporter_friendly_name' => WPFunctions::get()->__('MailPoet Email Clicks', 'mailpoet'), 'exporter_friendly_name' => WPFunctions::get()->__('MailPoet Email Clicks', 'mailpoet'),
'callback' => [new NewsletterClicksExporter(), 'export'], 'callback' => [ContainerWrapper::getInstance()->get(NewsletterClicksExporter::class), 'export'],
]; ];
return $exporters; return $exporters;
} }
@@ -55,7 +55,7 @@ class PersonalDataExporters {
public function registerNewsletterOpensExporter($exporters) { public function registerNewsletterOpensExporter($exporters) {
$exporters[] = [ $exporters[] = [
'exporter_friendly_name' => WPFunctions::get()->__('MailPoet Email Opens', 'mailpoet'), 'exporter_friendly_name' => WPFunctions::get()->__('MailPoet Email Opens', 'mailpoet'),
'callback' => [new NewsletterOpensExporter(), 'export'], 'callback' => [ContainerWrapper::getInstance()->get(NewsletterOpensExporter::class), 'export'],
]; ];
return $exporters; return $exporters;
} }

View File

@@ -265,6 +265,8 @@ class ContainerConfigurator implements IContainerConfigurator {
$container->autowire(\MailPoet\Subscribers\SubscriberSubscribeController::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\SubscriberSubscribeController::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\ImportExport\ImportExportRepository::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\ImportExport\ImportExportRepository::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\ImportExport\PersonalDataExporters\NewslettersExporter::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\ImportExport\PersonalDataExporters\NewslettersExporter::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\ImportExport\PersonalDataExporters\NewsletterOpensExporter::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\ImportExport\PersonalDataExporters\NewsletterClicksExporter::class)->setPublic(true);
$container->autowire(\MailPoet\Subscribers\Statistics\SubscriberStatisticsRepository::class); $container->autowire(\MailPoet\Subscribers\Statistics\SubscriberStatisticsRepository::class);
$container->autowire(\MailPoet\Subscribers\SubscribersCountsController::class)->setPublic(true); $container->autowire(\MailPoet\Subscribers\SubscribersCountsController::class)->setPublic(true);
// Segments // Segments

View File

@@ -3,9 +3,6 @@
namespace MailPoet\Models; namespace MailPoet\Models;
use DateTimeInterface; use DateTimeInterface;
use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\UserAgentEntity;
use MailPoetVendor\Doctrine\ORM\EntityManager;
/** /**
* @property int $newsletterId * @property int $newsletterId
@@ -17,35 +14,6 @@ use MailPoetVendor\Doctrine\ORM\EntityManager;
class StatisticsClicks extends Model { class StatisticsClicks extends Model {
public static $_table = MP_STATISTICS_CLICKS_TABLE; // phpcs:ignore PSR2.Classes.PropertyDeclaration public static $_table = MP_STATISTICS_CLICKS_TABLE; // phpcs:ignore PSR2.Classes.PropertyDeclaration
public static function getAllForSubscriber(Subscriber $subscriber) {
$entityManager = ContainerWrapper::getInstance()->get(EntityManager::class);
$userAgentsTable = $entityManager->getClassMetadata(UserAgentEntity::class)->getTableName();
return static::tableAlias('clicks')
->select('clicks.id', 'id')
->select('newsletter_rendered_subject')
->select('clicks.created_at', 'created_at')
->select('url')
->select('user_agent.user_agent')
->join(
SendingQueue::$_table,
['clicks.queue_id', '=', 'queue.id'],
'queue'
)
->join(
NewsletterLink::$_table,
['clicks.link_id', '=', 'link.id'],
'link'
)
->leftOuterJoin(
$userAgentsTable,
['clicks.user_agent_id', '=', 'user_agent.id'],
'user_agent'
)
->where('clicks.subscriber_id', $subscriber->id())
->orderByAsc('url');
}
public static function findLatestPerNewsletterBySubscriber(Subscriber $subscriber, DateTimeInterface $from, DateTimeInterface $to) { public static function findLatestPerNewsletterBySubscriber(Subscriber $subscriber, DateTimeInterface $from, DateTimeInterface $to) {
// subquery to find latest click IDs for each newsletter // subquery to find latest click IDs for each newsletter
$table = self::$_table; $table = self::$_table;

View File

@@ -2,10 +2,6 @@
namespace MailPoet\Models; namespace MailPoet\Models;
use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\UserAgentEntity;
use MailPoetVendor\Doctrine\ORM\EntityManager;
/** /**
* @property int $newsletterId * @property int $newsletterId
* @property int $subscriberId * @property int $subscriberId
@@ -28,27 +24,4 @@ class StatisticsOpens extends Model {
} }
return $statistics; return $statistics;
} }
public static function getAllForSubscriber(Subscriber $subscriber) {
$entityManager = ContainerWrapper::getInstance()->get(EntityManager::class);
$userAgentsTable = $entityManager->getClassMetadata(UserAgentEntity::class)->getTableName();
return static::tableAlias('opens')
->select('opens.id', 'id')
->select('newsletter_rendered_subject')
->select('opens.created_at', 'created_at')
->select('user_agent.user_agent')
->join(
SendingQueue::$_table,
['opens.queue_id', '=', 'queue.id'],
'queue'
)
->leftOuterJoin(
$userAgentsTable,
['opens.user_agent_id', '=', 'user_agent.id'],
'user_agent'
)
->where('opens.subscriber_id', $subscriber->id())
->orderByAsc('newsletter_rendered_subject');
}
} }

View File

@@ -9,6 +9,7 @@ use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\StatisticsClickEntity; use MailPoet\Entities\StatisticsClickEntity;
use MailPoet\Entities\SubscriberEntity; use MailPoet\Entities\SubscriberEntity;
use MailPoet\Entities\UserAgentEntity; use MailPoet\Entities\UserAgentEntity;
use MailPoetVendor\Doctrine\ORM\QueryBuilder;
/** /**
* @extends Repository<StatisticsClickEntity> * @extends Repository<StatisticsClickEntity>
@@ -43,4 +44,16 @@ class StatisticsClicksRepository extends Repository {
} }
return $statistics; return $statistics;
} }
public function getAllForSubscriber(SubscriberEntity $subscriber): QueryBuilder {
return $this->entityManager->createQueryBuilder()
->select('clicks.id id, queue.newsletterRenderedSubject, clicks.createdAt, link.url, userAgent.userAgent')
->from(StatisticsClickEntity::class, 'clicks')
->join('clicks.queue', 'queue')
->join('clicks.link', 'link')
->leftJoin('clicks.userAgent', 'userAgent')
->where('clicks.subscriber = :subscriber')
->orderBy('link.url')
->setParameter('subscriber', $subscriber->getId());
}
} }

View File

@@ -8,6 +8,7 @@ use MailPoet\Entities\StatisticsNewsletterEntity;
use MailPoet\Entities\StatisticsOpenEntity; use MailPoet\Entities\StatisticsOpenEntity;
use MailPoet\Entities\SubscriberEntity; use MailPoet\Entities\SubscriberEntity;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\ORM\QueryBuilder;
/** /**
* @extends Repository<StatisticsOpenEntity> * @extends Repository<StatisticsOpenEntity>
@@ -86,4 +87,15 @@ class StatisticsOpensRepository extends Repository {
->setParameter('updatedAt', null) ->setParameter('updatedAt', null)
->getQuery()->execute(); ->getQuery()->execute();
} }
public function getAllForSubscriber(SubscriberEntity $subscriber): QueryBuilder {
return $this->entityManager->createQueryBuilder()
->select('opens.id id, queue.newsletterRenderedSubject, opens.createdAt, userAgent.userAgent')
->from(StatisticsOpenEntity::class, 'opens')
->join('opens.queue', 'queue')
->leftJoin('opens.userAgent', 'userAgent')
->where('opens.subscriber = :subscriber')
->orderBy('queue.newsletterRenderedSubject')
->setParameter('subscriber', $subscriber->getId());
}
} }

View File

@@ -2,29 +2,29 @@
namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters; namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters;
use MailPoet\Models\StatisticsClicks; use MailPoet\Statistics\StatisticsClicksRepository;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
class NewsletterClicksExporter extends NewsletterStatsBaseExporter { class NewsletterClicksExporter extends NewsletterStatsBaseExporter {
protected $statsClass = StatisticsClicks::class; protected $statsClassName = StatisticsClicksRepository::class;
protected function getEmailStats(array $row) { protected function getEmailStats(array $row) {
$newsletterData = []; $newsletterData = [];
$newsletterData[] = [ $newsletterData[] = [
'name' => WPFunctions::get()->__('Email subject', 'mailpoet'), 'name' => WPFunctions::get()->__('Email subject', 'mailpoet'),
'value' => $row['newsletter_rendered_subject'], 'value' => $row['newsletterRenderedSubject'],
]; ];
$newsletterData[] = [ $newsletterData[] = [
'name' => WPFunctions::get()->__('Timestamp of the click event', 'mailpoet'), 'name' => WPFunctions::get()->__('Timestamp of the click event', 'mailpoet'),
'value' => $row['created_at'], 'value' => $row['createdAt']->format("Y-m-d H:i:s"),
]; ];
$newsletterData[] = [ $newsletterData[] = [
'name' => WPFunctions::get()->__('URL', 'mailpoet'), 'name' => WPFunctions::get()->__('URL', 'mailpoet'),
'value' => $row['url'], 'value' => $row['url'],
]; ];
if (!is_null($row['user_agent'])) { if (!is_null($row['userAgent'])) {
$userAgent = $row['user_agent']; $userAgent = $row['userAgent'];
} else { } else {
$userAgent = WPFunctions::get()->__('Unknown', 'mailpoet'); $userAgent = WPFunctions::get()->__('Unknown', 'mailpoet');
} }

View File

@@ -2,25 +2,25 @@
namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters; namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters;
use MailPoet\Models\StatisticsOpens; use MailPoet\Statistics\StatisticsOpensRepository;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
class NewsletterOpensExporter extends NewsletterStatsBaseExporter { class NewsletterOpensExporter extends NewsletterStatsBaseExporter {
protected $statsClass = StatisticsOpens::class; protected $statsClassName = StatisticsOpensRepository::class;
protected function getEmailStats(array $row): array { protected function getEmailStats(array $row): array {
$newsletterData = []; $newsletterData = [];
$newsletterData[] = [ $newsletterData[] = [
'name' => WPFunctions::get()->__('Email subject', 'mailpoet'), 'name' => WPFunctions::get()->__('Email subject', 'mailpoet'),
'value' => $row['newsletter_rendered_subject'], 'value' => $row['newsletterRenderedSubject'],
]; ];
$newsletterData[] = [ $newsletterData[] = [
'name' => WPFunctions::get()->__('Timestamp of the open event', 'mailpoet'), 'name' => WPFunctions::get()->__('Timestamp of the open event', 'mailpoet'),
'value' => $row['created_at'], 'value' => $row['createdAt']->format("Y-m-d H:i:s"),
]; ];
if (!is_null($row['user_agent'])) { if (!is_null($row['userAgent'])) {
$userAgent = $row['user_agent']; $userAgent = $row['userAgent'];
} else { } else {
$userAgent = WPFunctions::get()->__('Unknown', 'mailpoet'); $userAgent = WPFunctions::get()->__('Unknown', 'mailpoet');
} }

View File

@@ -2,33 +2,45 @@
namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters; namespace MailPoet\Subscribers\ImportExport\PersonalDataExporters;
use MailPoet\Models\Subscriber; use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Subscribers\SubscribersRepository;
abstract class NewsletterStatsBaseExporter { abstract class NewsletterStatsBaseExporter {
const LIMIT = 100; const LIMIT = 100;
protected $statsClass; protected $statsClassName;
protected $subscriberRepository;
public function __construct(SubscribersRepository $subscribersRepository) {
$this->subscriberRepository = $subscribersRepository;
}
public function export($email, $page = 1): array {
$data = [];
$subscriber = $this->subscriberRepository->findOneBy(['email' => trim($email)]);
if ($subscriber instanceof SubscriberEntity) {
$data = $this->getSubscriberData($subscriber, $page);
}
public function export($email, $page = 1) {
$data = $this->getSubscriberData(Subscriber::findOne(trim($email)), $page);
return [ return [
'data' => $data, 'data' => $data,
'done' => count($data) < self::LIMIT, 'done' => count($data) < self::LIMIT,
]; ];
} }
private function getSubscriberData($subscriber, $page) { private function getSubscriberData(SubscriberEntity $subscriber, $page): array {
if (!$subscriber) {
return [];
}
$result = []; $result = [];
$statistics = $this->statsClass::getAllForSubscriber($subscriber) $statsClass = ContainerWrapper::getInstance()->get($this->statsClassName);
->limit(self::LIMIT) $statistics = $statsClass->getAllForSubscriber($subscriber)
->offset(self::LIMIT * ($page - 1)) ->setMaxResults(self::LIMIT)
->findArray(); ->setFirstResult(self::LIMIT * ($page - 1))
->getQuery()
->getResult();
foreach ($statistics as $row) { foreach ($statistics as $row) {
$result[] = $this->getEmailStats($row); $result[] = $this->getEmailStats($row);

View File

@@ -20,7 +20,7 @@ class NewsletterClicksExporterTest extends \MailPoetTest {
public function _before() { public function _before() {
parent::_before(); parent::_before();
$this->exporter = new NewsletterClicksExporter(); $this->exporter = $this->diContainer->get(NewsletterClicksExporter::class);
} }
public function testExportWorksWhenSubscriberNotFound() { public function testExportWorksWhenSubscriberNotFound() {

View File

@@ -19,7 +19,7 @@ class NewsletterOpensExporterTest extends \MailPoetTest {
public function _before() { public function _before() {
parent::_before(); parent::_before();
$this->exporter = new NewsletterOpensExporter(); $this->exporter = $this->diContainer->get(NewsletterOpensExporter::class);
} }
public function testExportWorksWhenSubscriberNotFound() { public function testExportWorksWhenSubscriberNotFound() {