Implement email actions filter in Doctrine
[MAILPOET-3077]
This commit is contained in:
committed by
Veljko V
parent
cf76480ab3
commit
00db901d94
@ -257,6 +257,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\Segments\SubscribersFinder::class);
|
$container->autowire(\MailPoet\Segments\SubscribersFinder::class);
|
||||||
$container->autowire(\MailPoet\Segments\SegmentsRepository::class);
|
$container->autowire(\MailPoet\Segments\SegmentsRepository::class);
|
||||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\UserRole::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\UserRole::class)->setPublic(true);
|
||||||
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\EmailAction::class)->setPublic(true);
|
||||||
// Services
|
// Services
|
||||||
$container->autowire(\MailPoet\Services\Bridge::class)->setPublic(true);
|
$container->autowire(\MailPoet\Services\Bridge::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Services\AuthorizedEmailsController::class);
|
$container->autowire(\MailPoet\Services\AuthorizedEmailsController::class);
|
||||||
|
@ -19,6 +19,8 @@ class DynamicSegmentFilterEntity {
|
|||||||
use SafeToOneAssociationLoadTrait;
|
use SafeToOneAssociationLoadTrait;
|
||||||
|
|
||||||
const TYPE_USER_ROLE = 'userRole';
|
const TYPE_USER_ROLE = 'userRole';
|
||||||
|
const TYPE_EMAIL = 'email';
|
||||||
|
const TYPE_WOOCOMMERCE = 'woocommerce';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SegmentEntity", inversedBy="filters")
|
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SegmentEntity", inversedBy="filters")
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
|
use MailPoet\Entities\StatisticsClickEntity;
|
||||||
|
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||||
|
use MailPoet\Entities\StatisticsOpenEntity;
|
||||||
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
class EmailAction implements Filter {
|
class EmailAction implements Filter {
|
||||||
const ACTION_OPENED = 'opened';
|
const ACTION_OPENED = 'opened';
|
||||||
@ -10,22 +16,67 @@ class EmailAction implements Filter {
|
|||||||
const ACTION_CLICKED = 'clicked';
|
const ACTION_CLICKED = 'clicked';
|
||||||
const ACTION_NOT_CLICKED = 'notClicked';
|
const ACTION_NOT_CLICKED = 'notClicked';
|
||||||
|
|
||||||
/** @var string */
|
/** @var EntityManager */
|
||||||
private $action;
|
private $entityManager;
|
||||||
|
|
||||||
/** @var int */
|
public function __construct(EntityManager $entityManager) {
|
||||||
private $newsletterId;
|
$this->entityManager = $entityManager;
|
||||||
|
|
||||||
/** @var int|null */
|
|
||||||
private $linkId;
|
|
||||||
|
|
||||||
public function __construct(string $action, int $newsletterId, int $linkId = null) {
|
|
||||||
$this->action = $action;
|
|
||||||
$this->newsletterId = $newsletterId;
|
|
||||||
$this->linkId = $linkId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function apply(QueryBuilder $queryBuilder): QueryBuilder {
|
public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filterEntity): QueryBuilder {
|
||||||
|
$action = $filterEntity->getFilterDataParam('action');
|
||||||
|
$newsletterId = (int)$filterEntity->getFilterDataParam('newsletter_id');
|
||||||
|
$linkId = $filterEntity->getFilterDataParam('link_id') ? (int)$filterEntity->getFilterDataParam('link_id') : null;
|
||||||
|
|
||||||
|
$statsSentTable = $this->entityManager->getClassMetadata(StatisticsNewsletterEntity::class)->getTableName();
|
||||||
|
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||||
|
if (($action === self::ACTION_CLICKED) || ($action === self::ACTION_NOT_CLICKED)) {
|
||||||
|
$statsTable = $this->entityManager->getClassMetadata(StatisticsClickEntity::class)->getTableName();
|
||||||
|
} else {
|
||||||
|
$statsTable = $this->entityManager->getClassMetadata(StatisticsOpenEntity::class)->getTableName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = '1';
|
||||||
|
|
||||||
|
if (($action === self::ACTION_NOT_CLICKED) || ($action === self::ACTION_NOT_OPENED)) {
|
||||||
|
$queryBuilder = $queryBuilder->innerJoin(
|
||||||
|
$subscribersTable,
|
||||||
|
$statsSentTable,
|
||||||
|
'statssent',
|
||||||
|
"$subscribersTable.id = statssent.subscriber_id AND statssent.newsletter_id = :newsletter"
|
||||||
|
)->leftJoin(
|
||||||
|
'statssent',
|
||||||
|
$statsTable,
|
||||||
|
'stats',
|
||||||
|
$this->createNotStatsJoinCondition($action, $linkId)
|
||||||
|
);
|
||||||
|
$where .= ' AND stats.id IS NULL';
|
||||||
|
} else {
|
||||||
|
$queryBuilder = $queryBuilder->innerJoin(
|
||||||
|
$subscribersTable,
|
||||||
|
$statsTable,
|
||||||
|
'stats',
|
||||||
|
"stats.subscriber_id = $subscribersTable.id AND stats.newsletter_id = :newsletter"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($action === EmailAction::ACTION_CLICKED && $linkId) {
|
||||||
|
$where .= ' AND stats.link_id = :link';
|
||||||
|
}
|
||||||
|
$queryBuilder = $queryBuilder
|
||||||
|
->andWhere($where)
|
||||||
|
->setParameter('newsletter', $newsletterId);
|
||||||
|
if (in_array($action, [EmailAction::ACTION_CLICKED, EmailAction::ACTION_NOT_CLICKED]) && $linkId) {
|
||||||
|
$queryBuilder = $queryBuilder
|
||||||
|
->setParameter('link', $linkId);
|
||||||
|
}
|
||||||
return $queryBuilder;
|
return $queryBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createNotStatsJoinCondition(string $action, int $linkId = null): string {
|
||||||
|
$clause = "statssent.subscriber_id = stats.subscriber_id AND stats.newsletter_id = :newsletter";
|
||||||
|
if ($action === EmailAction::ACTION_NOT_CLICKED && $linkId) {
|
||||||
|
$clause .= ' AND stats.link_id = :link';
|
||||||
|
}
|
||||||
|
return $clause;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
|
use MailPoet\Entities\SegmentEntity;
|
||||||
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoet\Models\Newsletter;
|
||||||
|
use MailPoet\Models\StatisticsClicks;
|
||||||
|
use MailPoet\Models\StatisticsNewsletters;
|
||||||
|
use MailPoet\Models\StatisticsOpens;
|
||||||
|
use MailPoet\Models\Subscriber;
|
||||||
|
|
||||||
|
class EmailActionTest extends \MailPoetTest {
|
||||||
|
/** @var EmailAction */
|
||||||
|
private $emailAction;
|
||||||
|
|
||||||
|
public $subscriberOpenedNotClicked;
|
||||||
|
public $subscriberNotSent;
|
||||||
|
public $subscriberNotOpened;
|
||||||
|
public $subscriberOpenedClicked;
|
||||||
|
public $newsletter;
|
||||||
|
|
||||||
|
public function _before() {
|
||||||
|
$this->emailAction = $this->diContainer->get(EmailAction::class);
|
||||||
|
$this->newsletter = Newsletter::createOrUpdate([
|
||||||
|
'subject' => 'newsletter 1',
|
||||||
|
'status' => 'sent',
|
||||||
|
'type' => Newsletter::TYPE_NOTIFICATION,
|
||||||
|
]);
|
||||||
|
$this->subscriberOpenedClicked = Subscriber::createOrUpdate([
|
||||||
|
'email' => 'opened_clicked@example.com',
|
||||||
|
]);
|
||||||
|
$this->subscriberOpenedNotClicked = Subscriber::createOrUpdate([
|
||||||
|
'email' => 'opened_not_clicked@example.com',
|
||||||
|
]);
|
||||||
|
$this->subscriberNotOpened = Subscriber::createOrUpdate([
|
||||||
|
'email' => 'not_opened@example.com',
|
||||||
|
]);
|
||||||
|
$this->subscriberNotSent = Subscriber::createOrUpdate([
|
||||||
|
'email' => 'not_sent@example.com',
|
||||||
|
]);
|
||||||
|
StatisticsNewsletters::createMultiple([
|
||||||
|
['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriberOpenedClicked->id, 'queue_id' => 1],
|
||||||
|
['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriberOpenedNotClicked->id, 'queue_id' => 1],
|
||||||
|
['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriberNotOpened->id, 'queue_id' => 1],
|
||||||
|
]);
|
||||||
|
StatisticsOpens::getOrCreate($this->subscriberOpenedClicked->id, $this->newsletter->id, 1);
|
||||||
|
StatisticsOpens::getOrCreate($this->subscriberOpenedNotClicked->id, $this->newsletter->id, 1);
|
||||||
|
StatisticsClicks::createOrUpdateClickCount(1, $this->subscriberOpenedClicked->id, $this->newsletter->id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOpened() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_OPENED, $this->newsletter->id);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(2);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
$subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
assert($subscriber2 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_clicked@example.com');
|
||||||
|
expect($subscriber2->getEmail())->equals('opened_not_clicked@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotOpened() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_NOT_OPENED, $this->newsletter->id);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(1);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('not_opened@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClickedWithoutLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_CLICKED, $this->newsletter->id);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(1);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_clicked@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClickedWithLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_CLICKED, $this->newsletter->id, 1);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(1);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_clicked@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClickedWrongLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_CLICKED, $this->newsletter->id, 2);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetNotClickedWithLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id, 1);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(2);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
$subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
assert($subscriber2 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_not_clicked@example.com');
|
||||||
|
expect($subscriber2->getEmail())->equals('not_opened@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetNotClickedWithWrongLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id, 2);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(3);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
$subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
assert($subscriber2 instanceof SubscriberEntity);
|
||||||
|
$subscriber3 = $this->entityManager->find(SubscriberEntity::class, $result[2]['id']);
|
||||||
|
assert($subscriber3 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_clicked@example.com');
|
||||||
|
expect($subscriber2->getEmail())->equals('opened_not_clicked@example.com');
|
||||||
|
expect($subscriber3->getEmail())->equals('not_opened@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetNotClickedWithoutLink() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id);
|
||||||
|
$result = $this->emailAction->apply($this->getQueryBuilder(), $segmentFilter)->execute()->fetchAll();
|
||||||
|
expect(count($result))->equals(2);
|
||||||
|
$subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
assert($subscriber1 instanceof SubscriberEntity);
|
||||||
|
$subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
assert($subscriber2 instanceof SubscriberEntity);
|
||||||
|
expect($subscriber1->getEmail())->equals('opened_not_clicked@example.com');
|
||||||
|
expect($subscriber2->getEmail())->equals('not_opened@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQueryBuilder() {
|
||||||
|
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||||
|
return $this->entityManager
|
||||||
|
->getConnection()
|
||||||
|
->createQueryBuilder()
|
||||||
|
->select("$subscribersTable.id")
|
||||||
|
->from($subscribersTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSegmentFilter(string $action, int $newsletterId, int $linkId = null): DynamicSegmentFilterEntity {
|
||||||
|
return new DynamicSegmentFilterEntity(
|
||||||
|
new SegmentEntity('segment', SegmentEntity::TYPE_DYNAMIC),
|
||||||
|
[
|
||||||
|
'segmentType' => DynamicSegmentFilterEntity::TYPE_EMAIL,
|
||||||
|
'action' => $action,
|
||||||
|
'newsletter_id' => $newsletterId,
|
||||||
|
'link_id' => $linkId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _after() {
|
||||||
|
$this->cleanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanData() {
|
||||||
|
StatisticsClicks::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete();
|
||||||
|
StatisticsNewsletters::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete();
|
||||||
|
StatisticsOpens::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete();
|
||||||
|
$this->newsletter->delete();
|
||||||
|
$this->subscriberOpenedClicked->delete();
|
||||||
|
$this->subscriberOpenedNotClicked->delete();
|
||||||
|
$this->subscriberNotOpened->delete();
|
||||||
|
$this->subscriberNotSent->delete();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user