Enable joining segments using and/or
[MAILPOET-3212]
This commit is contained in:
@ -11,6 +11,7 @@ use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
|
|||||||
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
|
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
|
||||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
|
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
|
||||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
||||||
|
use MailPoet\Util\Security;
|
||||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
@ -61,7 +62,28 @@ class FilterHandler {
|
|||||||
$queryBuilder->getParameters()
|
$queryBuilder->getParameters()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$queryBuilder->innerJoin($subscribersTable, sprintf('(%s)', join(' UNION ', $filterSelects)), 'filtered_subscribers', 'filtered_subscribers.inner_subscriber_id = id');
|
$this->joinSubqueries($queryBuilder, $segment, $filterSelects);
|
||||||
|
return $queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function joinSubqueries(QueryBuilder $queryBuilder, SegmentEntity $segment, array $subQueries): QueryBuilder {
|
||||||
|
$filter = $segment->getDynamicFilters()->first();
|
||||||
|
if (!$filter) return $queryBuilder;
|
||||||
|
$filterData = $filter->getFilterData();
|
||||||
|
$data = $filterData->getData();
|
||||||
|
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||||
|
|
||||||
|
if (!isset($data['connect']) || $data['connect'] === 'or') {
|
||||||
|
// the final query: SELECT * FROM subscribers INNER JOIN (filter_select1 UNION filter_select2) filtered_subscribers ON filtered_subscribers.inner_subscriber_id = id
|
||||||
|
$queryBuilder->innerJoin($subscribersTable, sprintf('(%s)', join(' UNION ', $subQueries)), 'filtered_subscribers', 'filtered_subscribers.inner_subscriber_id = id');
|
||||||
|
return $queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($subQueries as $subQuery) {
|
||||||
|
// we need a unique name for each subquery so that we can join them together in the sql query - just make sure the identifier starts with a letter, not a number
|
||||||
|
$subqueryName = 'a' . Security::generateRandomString(5);
|
||||||
|
$queryBuilder->innerJoin($subscribersTable, "($subQuery)", $subqueryName, "$subqueryName.inner_subscriber_id = id");
|
||||||
|
}
|
||||||
return $queryBuilder;
|
return $queryBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
use MailPoet\Entities\StatisticsClickEntity;
|
use MailPoet\Entities\StatisticsClickEntity;
|
||||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
use MailPoet\WP\Functions as WPFunctions;
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
@ -4,14 +4,26 @@ namespace MailPoet\Segments\DynamicSegments;
|
|||||||
|
|
||||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||||
|
use MailPoet\Entities\NewsletterEntity;
|
||||||
|
use MailPoet\Entities\ScheduledTaskEntity;
|
||||||
use MailPoet\Entities\SegmentEntity;
|
use MailPoet\Entities\SegmentEntity;
|
||||||
|
use MailPoet\Entities\SendingQueueEntity;
|
||||||
|
use MailPoet\Entities\StatisticsOpenEntity;
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
|
||||||
|
use MailPoet\Subscribers\SubscribersRepository;
|
||||||
|
|
||||||
class FilterHandlerTest extends \MailPoetTest {
|
class FilterHandlerTest extends \MailPoetTest {
|
||||||
|
|
||||||
/** @var FilterHandler */
|
/** @var FilterHandler */
|
||||||
private $filterHandler;
|
private $filterHandler;
|
||||||
|
|
||||||
|
/** @var SubscriberEntity */
|
||||||
|
private $subscriber1;
|
||||||
|
|
||||||
|
/** @var SubscriberEntity */
|
||||||
|
private $subscriber2;
|
||||||
|
|
||||||
public function _before() {
|
public function _before() {
|
||||||
$this->cleanWpUsers();
|
$this->cleanWpUsers();
|
||||||
$this->filterHandler = $this->diContainer->get(FilterHandler::class);
|
$this->filterHandler = $this->diContainer->get(FilterHandler::class);
|
||||||
@ -30,6 +42,12 @@ class FilterHandlerTest extends \MailPoetTest {
|
|||||||
'email' => 'user-role-test3@example.com',
|
'email' => 'user-role-test3@example.com',
|
||||||
'wp_user_id' => $id,
|
'wp_user_id' => $id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// fetch entities
|
||||||
|
/** @var SubscribersRepository $subscribersRepository */
|
||||||
|
$subscribersRepository = $this->diContainer->get(SubscribersRepository::class);
|
||||||
|
$this->subscriber1 = $subscribersRepository->findOneBy(['email' => 'user-role-test1@example.com']);
|
||||||
|
$this->subscriber2 = $subscribersRepository->findOneBy(['email' => 'user-role-test2@example.com']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAppliesFilter() {
|
public function testItAppliesFilter() {
|
||||||
@ -43,7 +61,7 @@ class FilterHandlerTest extends \MailPoetTest {
|
|||||||
expect($subscriber2->getEmail())->equals('user-role-test3@example.com');
|
expect($subscriber2->getEmail())->equals('user-role-test3@example.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAppliesTwoFilters() {
|
public function testItAppliesTwoFiltersWithoutSpecifyingConnection() {
|
||||||
$segment = $this->getSegment('editor');
|
$segment = $this->getSegment('editor');
|
||||||
$filter = new DynamicSegmentFilterData([
|
$filter = new DynamicSegmentFilterData([
|
||||||
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
||||||
@ -52,11 +70,84 @@ class FilterHandlerTest extends \MailPoetTest {
|
|||||||
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filter);
|
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filter);
|
||||||
$this->entityManager->persist($dynamicSegmentFilter);
|
$this->entityManager->persist($dynamicSegmentFilter);
|
||||||
$segment->addDynamicFilter($dynamicSegmentFilter);
|
$segment->addDynamicFilter($dynamicSegmentFilter);
|
||||||
|
$this->entityManager->flush();
|
||||||
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
|
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
|
||||||
$result = $queryBuilder->execute()->fetchAll();
|
$result = $queryBuilder->execute()->fetchAll();
|
||||||
expect($result)->count(3);
|
expect($result)->count(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testItAppliesTwoFiltersWithOr() {
|
||||||
|
$segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description');
|
||||||
|
$this->entityManager->persist($segment);
|
||||||
|
$filterData = new DynamicSegmentFilterData([
|
||||||
|
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
||||||
|
'wordpressRole' => 'administrator',
|
||||||
|
'connect' => 'or',
|
||||||
|
]);
|
||||||
|
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filterData);
|
||||||
|
$this->entityManager->persist($dynamicSegmentFilter);
|
||||||
|
$segment->addDynamicFilter($dynamicSegmentFilter);
|
||||||
|
$filterData = new DynamicSegmentFilterData([
|
||||||
|
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
||||||
|
'wordpressRole' => 'editor',
|
||||||
|
'connect' => 'or',
|
||||||
|
]);
|
||||||
|
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filterData);
|
||||||
|
$this->entityManager->persist($dynamicSegmentFilter);
|
||||||
|
$segment->addDynamicFilter($dynamicSegmentFilter);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
|
||||||
|
$result = $queryBuilder->execute()->fetchAll();
|
||||||
|
expect($result)->count(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItAppliesTwoFiltersWithAnd() {
|
||||||
|
$segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description');
|
||||||
|
$this->entityManager->persist($segment);
|
||||||
|
// filter user is an editor
|
||||||
|
$editorData = new DynamicSegmentFilterData([
|
||||||
|
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
||||||
|
'wordpressRole' => 'editor',
|
||||||
|
'connect' => 'and',
|
||||||
|
]);
|
||||||
|
$filterEditor = new DynamicSegmentFilterEntity($segment, $editorData);
|
||||||
|
$this->entityManager->persist($filterEditor);
|
||||||
|
$segment->addDynamicFilter($filterEditor);
|
||||||
|
// filter user opened an email
|
||||||
|
$newsletter = new NewsletterEntity();
|
||||||
|
$task = new ScheduledTaskEntity();
|
||||||
|
$this->entityManager->persist($task);
|
||||||
|
$queue = new SendingQueueEntity();
|
||||||
|
$queue->setNewsletter($newsletter);
|
||||||
|
$queue->setTask($task);
|
||||||
|
$this->entityManager->persist($queue);
|
||||||
|
$newsletter->getQueues()->add($queue);
|
||||||
|
$newsletter->setSubject('newsletter 1');
|
||||||
|
$newsletter->setStatus('sent');
|
||||||
|
$newsletter->setType(NewsletterEntity::TYPE_STANDARD);
|
||||||
|
$this->entityManager->persist($newsletter);
|
||||||
|
$open = new StatisticsOpenEntity($newsletter, $queue, $this->subscriber1);
|
||||||
|
$this->entityManager->persist($open);
|
||||||
|
$open = new StatisticsOpenEntity($newsletter, $queue, $this->subscriber2);
|
||||||
|
$this->entityManager->persist($open);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$openedData = new DynamicSegmentFilterData([
|
||||||
|
'segmentType' => DynamicSegmentFilterData::TYPE_EMAIL,
|
||||||
|
'action' => EmailAction::ACTION_OPENED,
|
||||||
|
'newsletter_id' => $newsletter->getId(),
|
||||||
|
'connect' => 'and',
|
||||||
|
]);
|
||||||
|
$filterOpened = new DynamicSegmentFilterEntity($segment, $openedData);
|
||||||
|
$this->entityManager->persist($filterOpened);
|
||||||
|
$segment->addDynamicFilter($filterOpened);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
|
||||||
|
$result = $queryBuilder->execute()->fetchAll();
|
||||||
|
expect($result)->count(1);
|
||||||
|
}
|
||||||
|
|
||||||
private function getSegment(string $role): SegmentEntity {
|
private function getSegment(string $role): SegmentEntity {
|
||||||
$filter = new DynamicSegmentFilterData([
|
$filter = new DynamicSegmentFilterData([
|
||||||
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
|
||||||
@ -84,6 +175,11 @@ class FilterHandlerTest extends \MailPoetTest {
|
|||||||
$this->cleanWpUsers();
|
$this->cleanWpUsers();
|
||||||
$this->truncateEntity(SubscriberEntity::class);
|
$this->truncateEntity(SubscriberEntity::class);
|
||||||
$this->truncateEntity(SegmentEntity::class);
|
$this->truncateEntity(SegmentEntity::class);
|
||||||
|
$this->truncateEntity(DynamicSegmentFilterEntity::class);
|
||||||
|
$this->truncateEntity(NewsletterEntity::class);
|
||||||
|
$this->truncateEntity(StatisticsOpenEntity::class);
|
||||||
|
$this->truncateEntity(SendingQueueEntity::class);
|
||||||
|
$this->truncateEntity(ScheduledTaskEntity::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanWpUsers() {
|
private function cleanWpUsers() {
|
||||||
|
Reference in New Issue
Block a user