Add a filter for lists dynamic segment
[MAILPOET-3502]
This commit is contained in:
@@ -294,6 +294,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\MailPoetCustomFields::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\MailPoetCustomFields::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\SubscriberScore::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\SubscriberScore::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\SubscriberSubscribedDate::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\SubscriberSubscribedDate::class)->setPublic(true);
|
||||||
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\SubscriberSegment::class)->setPublic(true);
|
||||||
$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\WooCommerceCategory::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry::class)->setPublic(true);
|
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry::class)->setPublic(true);
|
||||||
|
@@ -17,6 +17,10 @@ class DynamicSegmentFilterData {
|
|||||||
public const CONNECT_TYPE_AND = 'and';
|
public const CONNECT_TYPE_AND = 'and';
|
||||||
public const CONNECT_TYPE_OR = 'or';
|
public const CONNECT_TYPE_OR = 'or';
|
||||||
|
|
||||||
|
const OPERATOR_ANY = 'any';
|
||||||
|
const OPERATOR_ALL = 'all';
|
||||||
|
const OPERATOR_NONE = 'none';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="serialized_array")
|
* @ORM\Column(type="serialized_array")
|
||||||
* @var array|null
|
* @var array|null
|
||||||
|
@@ -47,10 +47,16 @@ class FilterHandler {
|
|||||||
$this->filterFactory->getFilterForFilterEntity($filter)->apply($subscribersIdsQuery, $filter);
|
$this->filterFactory->getFilterForFilterEntity($filter)->apply($subscribersIdsQuery, $filter);
|
||||||
}
|
}
|
||||||
$filterSelects[] = $subscribersIdsQuery->getSQL();
|
$filterSelects[] = $subscribersIdsQuery->getSQL();
|
||||||
$queryBuilder->setParameters(array_merge(
|
$queryBuilder->setParameters(
|
||||||
$subscribersIdsQuery->getParameters(),
|
array_merge(
|
||||||
$queryBuilder->getParameters()
|
$subscribersIdsQuery->getParameters(),
|
||||||
));
|
$queryBuilder->getParameters()
|
||||||
|
),
|
||||||
|
array_merge(
|
||||||
|
$subscribersIdsQuery->getParameterTypes(),
|
||||||
|
$queryBuilder->getParameterTypes()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$this->joinSubqueries($queryBuilder, $segment, $filterSelects);
|
$this->joinSubqueries($queryBuilder, $segment, $filterSelects);
|
||||||
return $queryBuilder;
|
return $queryBuilder;
|
||||||
|
62
lib/Segments/DynamicSegments/Filters/SubscriberSegment.php
Normal file
62
lib/Segments/DynamicSegments/Filters/SubscriberSegment.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||||
|
use MailPoet\Util\Security;
|
||||||
|
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||||
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
|
class SubscriberSegment implements Filter {
|
||||||
|
const TYPE = 'subscribedToList';
|
||||||
|
|
||||||
|
/** @var EntityManager */
|
||||||
|
private $entityManager;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
EntityManager $entityManager
|
||||||
|
) {
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
|
||||||
|
$filterData = $filter->getFilterData();
|
||||||
|
$segments = $filterData->getParam('segments');
|
||||||
|
$operator = $filterData->getParam('operator');
|
||||||
|
$parameterSuffix = $filter->getId() ?: Security::generateRandomString();
|
||||||
|
$statusSubscribedParam = 'subscribed' . $parameterSuffix;
|
||||||
|
$segmentsParam = 'segments' . $parameterSuffix;
|
||||||
|
|
||||||
|
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||||
|
$subscriberSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
|
||||||
|
|
||||||
|
$queryBuilder->leftJoin(
|
||||||
|
$subscribersTable,
|
||||||
|
$subscriberSegmentTable,
|
||||||
|
'subscriber_segments',
|
||||||
|
"$subscribersTable.id = subscriber_segments.subscriber_id"
|
||||||
|
. ' AND subscriber_segments.status = :' . $statusSubscribedParam
|
||||||
|
. ' AND subscriber_segments.segment_id IN (:' . $segmentsParam . ')'
|
||||||
|
);
|
||||||
|
|
||||||
|
$queryBuilder->setParameter($statusSubscribedParam, SubscriberEntity::STATUS_SUBSCRIBED);
|
||||||
|
$queryBuilder->setParameter($segmentsParam, $segments, Connection::PARAM_INT_ARRAY);
|
||||||
|
|
||||||
|
if ($operator === DynamicSegmentFilterData::OPERATOR_NONE) {
|
||||||
|
$queryBuilder->andWhere('subscriber_segments.id IS NULL');
|
||||||
|
} else {
|
||||||
|
$queryBuilder->andWhere('subscriber_segments.id IS NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($operator === DynamicSegmentFilterData::OPERATOR_ALL) {
|
||||||
|
$queryBuilder->groupBy('subscriber_id');
|
||||||
|
$queryBuilder->having('COUNT(1) = ' . count($segments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $queryBuilder;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||||
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
|
use MailPoet\Entities\SegmentEntity;
|
||||||
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||||
|
use MailPoetVendor\Carbon\CarbonImmutable;
|
||||||
|
use MailPoetVendor\Doctrine\DBAL\Driver\Statement;
|
||||||
|
|
||||||
|
class SubscriberSegmentTest extends \MailPoetTest {
|
||||||
|
/** @var SubscriberSegment */
|
||||||
|
private $filter;
|
||||||
|
|
||||||
|
/** @var SegmentEntity */
|
||||||
|
private $segment1;
|
||||||
|
/** @var SegmentEntity */
|
||||||
|
private $segment2;
|
||||||
|
|
||||||
|
public function _before() {
|
||||||
|
$this->filter = $this->diContainer->get(SubscriberSegment::class);
|
||||||
|
|
||||||
|
$this->cleanUp();
|
||||||
|
|
||||||
|
$subscriber1 = new SubscriberEntity();
|
||||||
|
$subscriber1->setLastSubscribedAt(CarbonImmutable::now());
|
||||||
|
$subscriber1->setEmail('a1@example.com');
|
||||||
|
$this->entityManager->persist($subscriber1);
|
||||||
|
|
||||||
|
$subscriber2 = new SubscriberEntity();
|
||||||
|
$subscriber2->setLastSubscribedAt(CarbonImmutable::now());
|
||||||
|
$subscriber2->setEmail('a2@example.com');
|
||||||
|
$this->entityManager->persist($subscriber2);
|
||||||
|
|
||||||
|
$subscriber3 = new SubscriberEntity();
|
||||||
|
$subscriber3->setLastSubscribedAt(CarbonImmutable::now());
|
||||||
|
$subscriber3->setEmail('a3@example.com');
|
||||||
|
$this->entityManager->persist($subscriber3);
|
||||||
|
|
||||||
|
$this->segment1 = new SegmentEntity('Segment 1', SegmentEntity::TYPE_DEFAULT, 'Segment 1');
|
||||||
|
$this->segment2 = new SegmentEntity('Segment 2', SegmentEntity::TYPE_DEFAULT, 'Segment 2');
|
||||||
|
$this->entityManager->persist($this->segment1);
|
||||||
|
$this->entityManager->persist($this->segment2);
|
||||||
|
|
||||||
|
$this->entityManager->persist(new SubscriberSegmentEntity($this->segment1, $subscriber1, SubscriberEntity::STATUS_SUBSCRIBED));
|
||||||
|
|
||||||
|
$this->entityManager->persist(new SubscriberSegmentEntity($this->segment2, $subscriber1, SubscriberEntity::STATUS_SUBSCRIBED));
|
||||||
|
$this->entityManager->persist(new SubscriberSegmentEntity($this->segment2, $subscriber2, SubscriberEntity::STATUS_SUBSCRIBED));
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscribedAnyOf() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(DynamicSegmentFilterData::OPERATOR_ANY, [$this->segment1->getId(), $this->segment2->getId()]);
|
||||||
|
$statement = $this->filter->apply($this->getQueryBuilder(), $segmentFilter)
|
||||||
|
->orderBy('email')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Statement::class, $statement);
|
||||||
|
$result = $statement->fetchAll();
|
||||||
|
|
||||||
|
expect(count($result))->equals(2);
|
||||||
|
$subscriber = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||||
|
expect($subscriber->getEmail())->equals('a1@example.com');
|
||||||
|
$subscriber = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||||
|
expect($subscriber->getEmail())->equals('a2@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscribedAllOf() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(DynamicSegmentFilterData::OPERATOR_ALL, [$this->segment1->getId(), $this->segment2->getId()]);
|
||||||
|
$statement = $this->filter->apply($this->getQueryBuilder(), $segmentFilter)
|
||||||
|
->orderBy('email')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Statement::class, $statement);
|
||||||
|
$result = $statement->fetchAll();
|
||||||
|
|
||||||
|
expect(count($result))->equals(1);
|
||||||
|
$subscriber = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||||
|
expect($subscriber->getEmail())->equals('a1@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscribedNoneOf() {
|
||||||
|
$segmentFilter = $this->getSegmentFilter(DynamicSegmentFilterData::OPERATOR_NONE, [$this->segment1->getId()]);
|
||||||
|
$statement = $this->filter->apply($this->getQueryBuilder(), $segmentFilter)
|
||||||
|
->orderBy('email')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Statement::class, $statement);
|
||||||
|
$result = $statement->fetchAll();
|
||||||
|
|
||||||
|
expect(count($result))->equals(2);
|
||||||
|
$subscriber = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']);
|
||||||
|
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||||
|
expect($subscriber->getEmail())->equals('a2@example.com');
|
||||||
|
$subscriber = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']);
|
||||||
|
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||||
|
expect($subscriber->getEmail())->equals('a3@example.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSegmentFilter(string $operator, array $segments): DynamicSegmentFilterEntity {
|
||||||
|
$segmentFilterData = new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberSegment::TYPE, [
|
||||||
|
'operator' => $operator,
|
||||||
|
'segments' => $segments,
|
||||||
|
]);
|
||||||
|
$segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description');
|
||||||
|
$this->entityManager->persist($segment);
|
||||||
|
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $segmentFilterData);
|
||||||
|
$this->entityManager->persist($dynamicSegmentFilter);
|
||||||
|
$segment->addDynamicFilter($dynamicSegmentFilter);
|
||||||
|
return $dynamicSegmentFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQueryBuilder() {
|
||||||
|
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||||
|
return $this->entityManager
|
||||||
|
->getConnection()
|
||||||
|
->createQueryBuilder()
|
||||||
|
->select("DISTINCT $subscribersTable.id")
|
||||||
|
->from($subscribersTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanUp() {
|
||||||
|
$this->truncateEntity(SubscriberEntity::class);
|
||||||
|
$this->truncateEntity(SegmentEntity::class);
|
||||||
|
$this->truncateEntity(DynamicSegmentFilterEntity::class);
|
||||||
|
$this->truncateEntity(SubscriberSegmentEntity::class);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user