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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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');
|
||||||
}
|
}
|
||||||
|
@@ -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');
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
@@ -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() {
|
||||||
|
@@ -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() {
|
||||||
|
Reference in New Issue
Block a user