Add backend logic for number of reviews filter
MAILPOET-5413
This commit is contained in:
committed by
Aschepikov
parent
c3bb4d1433
commit
ce9cbdc45d
@ -454,6 +454,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceCustomerTextField::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceMembership::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchaseDate::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue::class)->setPublic(true);
|
||||
|
@ -9,6 +9,7 @@ use MailPoet\Segments\DynamicSegments\Filters\DateFilterHelper;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailActionClickAny;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailOpensAbsoluteCountAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\FilterHelper;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\MailPoetCustomFields;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberDateField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberScore;
|
||||
@ -22,6 +23,7 @@ use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCustomerTextField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceMembership;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchaseDate;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue;
|
||||
@ -38,12 +40,22 @@ class FilterDataMapper {
|
||||
/** @var DateFilterHelper */
|
||||
private $dateFilterHelper;
|
||||
|
||||
/** @var WooCommerceNumberOfReviews */
|
||||
private $wooCommerceNumberOfReviews;
|
||||
|
||||
/** @var FilterHelper */
|
||||
private $filterHelper;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
DateFilterHelper $dateFilterHelper
|
||||
DateFilterHelper $dateFilterHelper,
|
||||
FilterHelper $filterHelper,
|
||||
WooCommerceNumberOfReviews $wooCommerceNumberOfReviews
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->dateFilterHelper = $dateFilterHelper;
|
||||
$this->filterHelper = $filterHelper;
|
||||
$this->wooCommerceNumberOfReviews = $wooCommerceNumberOfReviews;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,7 +324,7 @@ class FilterDataMapper {
|
||||
if (!isset($data['opens'])) {
|
||||
throw new InvalidFilterException('Missing number of opens', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
$this->validateDaysPeriodData($data);
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
$filterData = [
|
||||
'opens' => $data['opens'],
|
||||
'days' => $data['days'] ?? 0,
|
||||
@ -362,7 +374,7 @@ class FilterDataMapper {
|
||||
$filterData['country_code'] = $data['country_code'];
|
||||
$filterData['operator'] = $data['operator'] ?? DynamicSegmentFilterData::OPERATOR_ANY;
|
||||
} elseif ($data['action'] === WooCommerceNumberOfOrders::ACTION_NUMBER_OF_ORDERS) {
|
||||
$this->validateDaysPeriodData($data);
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
if (
|
||||
!isset($data['number_of_orders_type'])
|
||||
|| !isset($data['number_of_orders_count']) || $data['number_of_orders_count'] < 0
|
||||
@ -373,8 +385,15 @@ class FilterDataMapper {
|
||||
$filterData['number_of_orders_count'] = $data['number_of_orders_count'];
|
||||
$filterData['days'] = $data['days'] ?? 0;
|
||||
$filterData['timeframe'] = $data['timeframe'];
|
||||
} elseif ($data['action'] === WooCommerceNumberOfReviews::ACTION) {
|
||||
$this->wooCommerceNumberOfReviews->validateFilterData($data);
|
||||
$filterData['days'] = $data['days'];
|
||||
$filterData['count_type'] = $data['count_type'];
|
||||
$filterData['count'] = $data['count'];
|
||||
$filterData['rating'] = $data['rating'];
|
||||
$filterData['timeframe'] = $data['timeframe'];
|
||||
} elseif ($data['action'] === WooCommerceTotalSpent::ACTION_TOTAL_SPENT) {
|
||||
$this->validateDaysPeriodData($data);
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
if (
|
||||
!isset($data['total_spent_type'])
|
||||
|| !isset($data['total_spent_amount']) || $data['total_spent_amount'] < 0
|
||||
@ -386,7 +405,7 @@ class FilterDataMapper {
|
||||
$filterData['days'] = $data['days'] ?? 0;
|
||||
$filterData['timeframe'] = $data['timeframe'];
|
||||
} elseif ($data['action'] === WooCommerceSingleOrderValue::ACTION_SINGLE_ORDER_VALUE) {
|
||||
$this->validateDaysPeriodData($data);
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
if (
|
||||
!isset($data['single_order_value_type'])
|
||||
|| !isset($data['single_order_value_amount']) || $data['single_order_value_amount'] < 0
|
||||
@ -401,7 +420,7 @@ class FilterDataMapper {
|
||||
$filterData['operator'] = $data['operator'];
|
||||
$filterData['value'] = $data['value'];
|
||||
} elseif ($data['action'] === WooCommerceAverageSpent::ACTION) {
|
||||
$this->validateDaysPeriodData($data);
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
if (
|
||||
!isset($data['average_spent_type'])
|
||||
|| !isset($data['average_spent_amount']) || $data['average_spent_amount'] < 0
|
||||
@ -510,20 +529,4 @@ class FilterDataMapper {
|
||||
}
|
||||
return new DynamicSegmentFilterData($filterType, $action, $filterData);
|
||||
}
|
||||
|
||||
private function validateDaysPeriodData(array $data): void {
|
||||
if (!isset($data['timeframe']) || !in_array($data['timeframe'], [DynamicSegmentFilterData::TIMEFRAME_ALL_TIME, DynamicSegmentFilterData::TIMEFRAME_IN_THE_LAST], true)) {
|
||||
throw new InvalidFilterException('Missing timeframe type', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
|
||||
if ($data['timeframe'] === DynamicSegmentFilterData::TIMEFRAME_ALL_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
$days = intval($data['days'] ?? null);
|
||||
|
||||
if ($days < 1) {
|
||||
throw new InvalidFilterException('Missing number of days', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCustomerTextField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceMembership;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchaseDate;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue;
|
||||
@ -108,6 +109,9 @@ class FilterFactory {
|
||||
/** @var AutomationsEvents */
|
||||
private $automationsEvents;
|
||||
|
||||
/** @var WooCommerceNumberOfReviews */
|
||||
private $wooCommerceNumberOfReviews;
|
||||
|
||||
public function __construct(
|
||||
EmailAction $emailAction,
|
||||
EmailActionClickAny $emailActionClickAny,
|
||||
@ -119,6 +123,7 @@ class FilterFactory {
|
||||
WooCommerceCustomerTextField $wooCommerceCustomerTextField,
|
||||
EmailOpensAbsoluteCountAction $emailOpensAbsoluteCount,
|
||||
WooCommerceNumberOfOrders $wooCommerceNumberOfOrders,
|
||||
WooCommerceNumberOfReviews $wooCommerceNumberOfReviews,
|
||||
WooCommerceTotalSpent $wooCommerceTotalSpent,
|
||||
WooCommerceMembership $wooCommerceMembership,
|
||||
WooCommercePurchaseDate $wooCommercePurchaseDate,
|
||||
@ -141,6 +146,7 @@ class FilterFactory {
|
||||
$this->wooCommerceCategory = $wooCommerceCategory;
|
||||
$this->wooCommerceCountry = $wooCommerceCountry;
|
||||
$this->wooCommerceNumberOfOrders = $wooCommerceNumberOfOrders;
|
||||
$this->wooCommerceNumberOfReviews = $wooCommerceNumberOfReviews;
|
||||
$this->wooCommerceMembership = $wooCommerceMembership;
|
||||
$this->wooCommercePurchaseDate = $wooCommercePurchaseDate;
|
||||
$this->wooCommerceSubscription = $wooCommerceSubscription;
|
||||
@ -253,6 +259,8 @@ class FilterFactory {
|
||||
return $this->wooCommerceUsedPaymentMethod;
|
||||
} elseif ($action === WooCommerceUsedShippingMethod::ACTION) {
|
||||
return $this->wooCommerceUsedShippingMethod;
|
||||
} elseif ($action === WooCommerceNumberOfReviews::ACTION) {
|
||||
return $this->wooCommerceNumberOfReviews;
|
||||
} elseif (in_array($action, WooCommerceCustomerTextField::ACTIONS)) {
|
||||
return $this->wooCommerceCustomerTextField;
|
||||
}
|
||||
|
@ -2,13 +2,15 @@
|
||||
|
||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class FilterHelper {
|
||||
/** @var EntityManager */
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(
|
||||
@ -60,4 +62,20 @@ class FilterHelper {
|
||||
$suffix = Security::generateRandomString();
|
||||
return sprintf("%s_%s", $parameter, $suffix);
|
||||
}
|
||||
|
||||
public function validateDaysPeriodData(array $data): void {
|
||||
if (!isset($data['timeframe']) || !in_array($data['timeframe'], [DynamicSegmentFilterData::TIMEFRAME_ALL_TIME, DynamicSegmentFilterData::TIMEFRAME_IN_THE_LAST], true)) {
|
||||
throw new InvalidFilterException('Missing timeframe type', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
|
||||
if ($data['timeframe'] === DynamicSegmentFilterData::TIMEFRAME_ALL_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
$days = intval($data['days'] ?? null);
|
||||
|
||||
if ($days < 1) {
|
||||
throw new InvalidFilterException('Missing number of days', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,129 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
||||
use MailPoet\Util\DBCollationChecker;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||
|
||||
class WooCommerceNumberOfReviews implements Filter {
|
||||
const ACTION = 'numberOfReviews';
|
||||
|
||||
/** @var DBCollationChecker */
|
||||
private $collationChecker;
|
||||
|
||||
/** @var FilterHelper */
|
||||
private $filterHelper;
|
||||
|
||||
public function __construct(
|
||||
DBCollationChecker $collationChecker,
|
||||
FilterHelper $filterHelper
|
||||
) {
|
||||
$this->collationChecker = $collationChecker;
|
||||
$this->filterHelper = $filterHelper;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
|
||||
$commentsTable = $this->filterHelper->getPrefixedTable('comments');
|
||||
$commentMetaTable = $this->filterHelper->getPrefixedTable('commentmeta');
|
||||
$filterData = $filter->getFilterData();
|
||||
$this->validateFilterData((array)$filterData->getData());
|
||||
$type = strval($filterData->getParam('count_type'));
|
||||
$rating = strval($filterData->getParam('rating'));
|
||||
$days = intval($filterData->getParam('days'));
|
||||
$count = intval($filterData->getParam('count'));
|
||||
|
||||
$subscribersTable = $this->filterHelper->getSubscribersTable();
|
||||
$collation = $this->collationChecker->getCollateIfNeeded(
|
||||
$subscribersTable,
|
||||
'email',
|
||||
$commentsTable,
|
||||
'comment_author_email'
|
||||
);
|
||||
|
||||
$isAllTime = $filterData->getParam('timeframe') === DynamicSegmentFilterData::TIMEFRAME_ALL_TIME;
|
||||
$joinCondition = "$subscribersTable.email = comments.comment_author_email $collation
|
||||
AND comments.comment_type = 'review'";
|
||||
|
||||
if (!$isAllTime) {
|
||||
$date = Carbon::now()->subDays($days);
|
||||
$dateParam = $this->filterHelper->getUniqueParameterName('date');
|
||||
$joinCondition .= " AND comments.comment_date >= :$dateParam";
|
||||
$queryBuilder->setParameter($dateParam, $date->toDateTimeString());
|
||||
}
|
||||
|
||||
$commentMetaJoinCondition = "comments.comment_ID = commentmeta.comment_id AND commentmeta.meta_key = 'rating'";
|
||||
|
||||
if ($rating !== 'any') {
|
||||
$ratingParam = $this->filterHelper->getUniqueParameterName('rating');
|
||||
$commentMetaJoinCondition .= "AND commentmeta.meta_value = :$ratingParam";
|
||||
$queryBuilder->setParameter($ratingParam, $rating);
|
||||
}
|
||||
|
||||
$queryBuilder
|
||||
->leftJoin(
|
||||
$subscribersTable,
|
||||
$commentsTable,
|
||||
'comments',
|
||||
$joinCondition
|
||||
)->leftJoin(
|
||||
'comments',
|
||||
$commentMetaTable,
|
||||
'commentmeta',
|
||||
$commentMetaJoinCondition
|
||||
);
|
||||
|
||||
$queryBuilder->groupBy('inner_subscriber_id');
|
||||
|
||||
$countParam = $this->filterHelper->getUniqueParameterName('count');
|
||||
|
||||
switch ($type) {
|
||||
case '=':
|
||||
$queryBuilder->having("COUNT(commentmeta.meta_value) = :$countParam");
|
||||
break;
|
||||
case '!=':
|
||||
$queryBuilder->having("COUNT(commentmeta.meta_value) != :$countParam");
|
||||
break;
|
||||
case '>':
|
||||
$queryBuilder->having("COUNT(commentmeta.meta_value) > :$countParam");
|
||||
break;
|
||||
case '<':
|
||||
$queryBuilder->having("COUNT(commentmeta.meta_value) < :$countParam");
|
||||
break;
|
||||
}
|
||||
|
||||
$queryBuilder->setParameter($countParam, $count, 'integer');
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
public function validateFilterData(array $data): void {
|
||||
if (!isset($data['rating'])) {
|
||||
throw new InvalidFilterException('Missing rating', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
$validRatings = ['1', '2', '3', '4', '5', 'any'];
|
||||
if (!in_array($data['rating'], $validRatings, true)) {
|
||||
throw new InvalidFilterException('Invalid rating', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
if (!isset($data['count_type'])) {
|
||||
throw new InvalidFilterException('Missing count type', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
$type = $data['count_type'];
|
||||
$validTypes = [
|
||||
'=',
|
||||
'!=',
|
||||
'>',
|
||||
'<',
|
||||
];
|
||||
if (!in_array($type, $validTypes, true)) {
|
||||
throw new InvalidFilterException('Invalid count type', InvalidFilterException::INVALID_TYPE);
|
||||
}
|
||||
|
||||
if (!isset($data['count'])) {
|
||||
throw new InvalidFilterException('Missing review count', InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
$this->filterHelper->validateDaysPeriodData($data);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ use MailPoet\InvalidStateException;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\Filter;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\Statement;
|
||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
@ -57,6 +58,8 @@ class IntegrationTester extends \Codeception\Actor {
|
||||
|
||||
private $createdUsers = [];
|
||||
|
||||
private $createdCommentIds = [];
|
||||
|
||||
public function __construct(
|
||||
Scenario $scenario
|
||||
) {
|
||||
@ -177,6 +180,28 @@ class IntegrationTester extends \Codeception\Actor {
|
||||
return $order;
|
||||
}
|
||||
|
||||
public function createWooProductReview(int $customerId, string $customerEmail, int $productId, int $rating, Carbon $date = null): int {
|
||||
if ($date === null) {
|
||||
$date = Carbon::now()->subDay();
|
||||
}
|
||||
$commentId = wp_insert_comment([
|
||||
'comment_type' => 'review',
|
||||
'user_id' => $customerId,
|
||||
'comment_author_email' => $customerEmail,
|
||||
'comment_post_ID' => $productId,
|
||||
'comment_parent' => 0,
|
||||
'comment_date' => $date->toDateTimeString(),
|
||||
'comment_approved' => 1,
|
||||
'comment_content' => "This is a $rating star review",
|
||||
]);
|
||||
if (!is_int($commentId)) {
|
||||
throw new \Exception('Failed to insert review comment');
|
||||
}
|
||||
add_comment_meta($commentId, 'rating', $rating, true);
|
||||
$this->createdCommentIds[] = $commentId;
|
||||
return $commentId;
|
||||
}
|
||||
|
||||
public function createWooCommerceCoupon(array $data): void {
|
||||
$coupon = new WC_Coupon();
|
||||
|
||||
@ -348,8 +373,16 @@ class IntegrationTester extends \Codeception\Actor {
|
||||
public function cleanup() {
|
||||
$this->deleteWordPressTerms();
|
||||
$this->deleteCreatedUsers();
|
||||
$this->deleteCreatedComments();
|
||||
$this->deleteTestWooProducts();
|
||||
$this->deleteTestWooOrders();
|
||||
$this->deleteTestWooCoupons();
|
||||
}
|
||||
|
||||
private function deleteCreatedComments() {
|
||||
foreach ($this->createdCommentIds as $commentId) {
|
||||
wp_delete_comment($commentId, true);
|
||||
}
|
||||
$this->createdCommentIds = [];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,239 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace integration\Segments\DynamicSegments\Filters;
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* @group woo
|
||||
*/
|
||||
class WooCommerceNumberOfReviewsTest extends \MailPoetTest {
|
||||
/** @var WooCommerceNumberOfReviews */
|
||||
private $filter;
|
||||
|
||||
/** @var int */
|
||||
private $productId;
|
||||
|
||||
public function _before(): void {
|
||||
$this->filter = $this->diContainer->get(WooCommerceNumberOfReviews::class);
|
||||
$this->productId = $this->tester->createWooCommerceProduct(['name' => 'Some really fantastic product'])->get_id();
|
||||
}
|
||||
|
||||
public function testAnyRatingOnlyReturnsSubscribersWithRating(): void {
|
||||
$customerId = $this->tester->createCustomer('1@e.com');
|
||||
$this->tester->createCustomer('noreview@e.com');
|
||||
$this->tester->createWooProductReview($customerId, '1@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->assertFilterReturnsEmails('any', '>', 0, 200, 'inTheLast', ['1@e.com']);
|
||||
}
|
||||
|
||||
public function testItWorksWithOverAllTimeOption(): void {
|
||||
$customerId = $this->tester->createCustomer('1@e.com');
|
||||
$this->tester->createWooProductReview($customerId, '1@e.com', $this->productId, 1, Carbon::now()->subDays(20));
|
||||
$this->assertFilterReturnsEmails('any', '>', 0, 2, 'inTheLast', []);
|
||||
$this->assertFilterReturnsEmails('any', '>', 0, 2, 'allTime', ['1@e.com']);
|
||||
}
|
||||
|
||||
public function testItHandlesExactNumberOfReviews(): void {
|
||||
$customerId = $this->tester->createCustomer('test1@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'test1@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->tester->createWooProductReview($customerId, 'test1@e.com', $this->productId, 2, Carbon::now()->subDay());
|
||||
$this->assertFilterReturnsEmails('any', '=', 2, 200, 'inTheLast', ['test1@e.com']);
|
||||
}
|
||||
|
||||
public function testItHandlesGreaterThan(): void {
|
||||
$customerId = $this->tester->createCustomer('greaterThanTest@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'greaterthantest@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->tester->createWooProductReview($customerId, 'greaterthantest@e.com', $this->productId, 2, Carbon::now()->subDay());
|
||||
$customerId = $this->tester->createCustomer('oneReviewTest@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'onereviewtest@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->assertFilterReturnsEmails('any', '>', 1, 200, 'inTheLast', ['greaterthantest@e.com']);
|
||||
}
|
||||
|
||||
public function testItHandlesLessThan(): void {
|
||||
$this->tester->createCustomer('lessthantest@e.com');
|
||||
$customerId = $this->tester->createCustomer('onereviewtest@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'onereviewtest@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$customerId2 = $this->tester->createCustomer('tworeviews@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'tworeviews@e.com', $this->productId, 2, Carbon::now()->subDay());
|
||||
$this->tester->createWooProductReview($customerId, 'tworeviews@e.com', $this->productId, 2, Carbon::now()->subDay());
|
||||
$this->assertFilterReturnsEmails('any', '<', 1, 200, 'inTheLast', ['lessthantest@e.com']);
|
||||
$this->assertFilterReturnsEmails('any', '<', 2, 200, 'inTheLast', ['lessthantest@e.com', 'onereviewtest@e.com']);
|
||||
}
|
||||
|
||||
public function testItHandlesNotEquals(): void {
|
||||
$this->tester->createCustomer('notequalstest@e.com');
|
||||
$customerId = $this->tester->createCustomer('onereviewtest@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'onereviewtest@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$customerId = $this->tester->createCustomer('tworeviewstest@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'tworeviewstest@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->tester->createWooProductReview($customerId, 'tworeviewstest@e.com', $this->productId, 2, Carbon::now()->subDay());
|
||||
|
||||
$this->assertFilterReturnsEmails('any', '!=', 1, 200, 'inTheLast', ['notequalstest@e.com', 'tworeviewstest@e.com']);
|
||||
}
|
||||
|
||||
public function testItHandlesCustomerWithNoReviews(): void {
|
||||
$this->tester->createCustomer('test2@e.com');
|
||||
$this->assertFilterReturnsEmails('any', '>', 0, 200, 'inTheLast', []);
|
||||
}
|
||||
|
||||
public function testItIncludesCustomersWithNoReviewsWhenUsingLessThan(): void {
|
||||
$this->tester->createCustomer('test1@e.com');
|
||||
$customerId = $this->tester->createCustomer('test2@e.com');
|
||||
$this->tester->createWooProductReview($customerId, 'test2@e.com', $this->productId, 1, Carbon::now()->subDay());
|
||||
$this->assertFilterReturnsEmails('any', '<', 1, 200, 'inTheLast', ['test1@e.com']);
|
||||
}
|
||||
|
||||
public function testFiltersByDifferentRatings(): void {
|
||||
$customerOneId = $this->tester->createCustomer('customer-one@test.com');
|
||||
$this->tester->createWooProductReview($customerOneId, 'customer-one@test.com', $this->productId, 5);
|
||||
$this->tester->createWooProductReview($customerOneId, 'customer-one@test.com', $this->productId, 5);
|
||||
$this->tester->createWooProductReview($customerOneId, 'customer-one@test.com', $this->productId, 3);
|
||||
|
||||
$customerTwoId = $this->tester->createCustomer('customer-two@test.com');
|
||||
$this->tester->createWooProductReview($customerTwoId, 'customer-two@test.com', $this->productId, 4);
|
||||
$this->tester->createWooProductReview($customerTwoId, 'customer-two@test.com', $this->productId, 4);
|
||||
$this->tester->createWooProductReview($customerTwoId, 'customer-two@test.com', $this->productId, 4);
|
||||
|
||||
$customerThreeId = $this->tester->createCustomer('customer-three@test.com');
|
||||
$this->tester->createWooProductReview($customerThreeId, 'customer-three@test.com', $this->productId, 2);
|
||||
$this->tester->createWooProductReview($customerThreeId, 'customer-three@test.com', $this->productId, 5);
|
||||
|
||||
$this->assertFilterReturnsEmails('5', '>', 1, 200, 'inTheLast', ['customer-one@test.com']);
|
||||
$this->assertFilterReturnsEmails('4', '=', 3, 200, 'inTheLast', ['customer-two@test.com']);
|
||||
$this->assertFilterReturnsEmails('2', '=', 1, 200, 'inTheLast', ['customer-three@test.com']);
|
||||
}
|
||||
|
||||
public function testFiltersByDifferentDates(): void {
|
||||
$customerFourId = $this->tester->createCustomer('1@e.com');
|
||||
$this->tester->createWooProductReview($customerFourId, '1@e.com', $this->productId, 5, Carbon::now()->subDays(6));
|
||||
|
||||
$customerFiveId = $this->tester->createCustomer('2@e.com');
|
||||
$this->tester->createWooProductReview($customerFiveId, '2@e.com', $this->productId, 5, Carbon::now()->subWeeks(3));
|
||||
|
||||
$customerSixId = $this->tester->createCustomer('3@e.com');
|
||||
$this->tester->createWooProductReview($customerSixId, '3@e.com', $this->productId, 4, Carbon::now()->subWeeks(6));
|
||||
|
||||
$this->assertFilterReturnsEmails('5', '=', 1, 7, 'inTheLast', ['1@e.com']);
|
||||
$this->assertFilterReturnsEmails('5', '=', 1, 30, 'inTheLast', ['1@e.com', '2@e.com']);
|
||||
$this->assertFilterReturnsEmails('4', '=', 1, 50, 'inTheLast', ['3@e.com']);
|
||||
}
|
||||
|
||||
public function testItValidatesRatingPresence(): void {
|
||||
$this->expectException(InvalidFilterException::class);
|
||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
||||
$this->expectExceptionMessage('Missing rating');
|
||||
$this->filter->validateFilterData([
|
||||
'action' => 'numberOfReviews',
|
||||
'count_type' => '!=',
|
||||
'days' => '10',
|
||||
'count' => '3',
|
||||
'timeframe' => 'inTheLast',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ratingDataProvider
|
||||
*/
|
||||
public function testItValidatesRatingValue(string $rating, bool $shouldFail): void {
|
||||
$data = [
|
||||
'action' => 'numberOfReviews',
|
||||
'rating' => $rating,
|
||||
'days' => '10',
|
||||
'count' => '2',
|
||||
'count_type' => '!=',
|
||||
'timeframe' => 'inTheLast',
|
||||
];
|
||||
|
||||
if ($shouldFail) {
|
||||
$this->expectException(InvalidFilterException::class);
|
||||
$this->expectExceptionMessage('Invalid rating');
|
||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
||||
}
|
||||
|
||||
$this->filter->validateFilterData($data);
|
||||
}
|
||||
|
||||
public function ratingDataProvider(): array {
|
||||
return [
|
||||
'Invalid rating value' => ['6', true],
|
||||
'Valid rating value 1' => ['1', false],
|
||||
'Valid rating value 2' => ['2', false],
|
||||
'Valid rating value 3' => ['3', false],
|
||||
'Valid rating value 4' => ['4', false],
|
||||
'Valid rating value 5' => ['5', false],
|
||||
'Valid rating value any' => ['any', false],
|
||||
];
|
||||
}
|
||||
|
||||
public function testItValidatesCountType(): void {
|
||||
$this->expectException(InvalidFilterException::class);
|
||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
||||
$this->expectExceptionMessage('Missing count type');
|
||||
$this->filter->validateFilterData([
|
||||
'action' => 'numberOfReviews',
|
||||
'rating' => '3',
|
||||
'days' => '10',
|
||||
'count' => '3',
|
||||
'timeframe' => 'inTheLast',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testItValidatesCountTypeOptions(): void {
|
||||
$this->expectException(InvalidFilterException::class);
|
||||
$this->expectExceptionCode(InvalidFilterException::INVALID_TYPE);
|
||||
$this->expectExceptionMessage('Invalid count type');
|
||||
$this->filter->validateFilterData([
|
||||
'action' => 'numberOfReviews',
|
||||
'rating' => '3',
|
||||
'days' => '10',
|
||||
'count' => '3',
|
||||
'count_type' => 'invalid',
|
||||
'timeframe' => 'inTheLast',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testItValidatesCount(): void {
|
||||
$this->expectException(InvalidFilterException::class);
|
||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
||||
$this->expectExceptionMessage('Missing review count');
|
||||
$this->filter->validateFilterData([
|
||||
'action' => 'numberOfReviews',
|
||||
'count_type' => '!=',
|
||||
'rating' => '3',
|
||||
'days' => '10',
|
||||
'timeframe' => 'inTheLast',
|
||||
]);
|
||||
}
|
||||
|
||||
public function _after(): void {
|
||||
parent::_after();
|
||||
$this->cleanUp();
|
||||
}
|
||||
|
||||
private function assertFilterReturnsEmails(string $rating, string $countType, int $count, int $days, string $timeframe, array $expectedEmails): void {
|
||||
$data = new DynamicSegmentFilterData(
|
||||
DynamicSegmentFilterData::TYPE_WOOCOMMERCE,
|
||||
WooCommerceNumberOfReviews::ACTION,
|
||||
[
|
||||
'days' => $days,
|
||||
'count_type' => $countType,
|
||||
'count' => $count,
|
||||
'timeframe' => $timeframe,
|
||||
'rating' => $rating,
|
||||
]
|
||||
);
|
||||
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($data, $this->filter);
|
||||
$this->assertEqualsCanonicalizing($expectedEmails, $emails);
|
||||
}
|
||||
|
||||
private function cleanUp(): void {
|
||||
global $wpdb;
|
||||
$this->connection->executeQuery("TRUNCATE TABLE {$wpdb->prefix}wc_customer_lookup");
|
||||
$this->connection->executeQuery("TRUNCATE TABLE {$wpdb->prefix}wc_order_stats");
|
||||
$this->connection->executeQuery("TRUNCATE TABLE {$wpdb->prefix}comments");
|
||||
$this->connection->executeQuery("TRUNCATE TABLE {$wpdb->prefix}commentmeta");
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ use MailPoet\Segments\DynamicSegments\Filters\DateFilterHelper;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailActionClickAny;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailOpensAbsoluteCountAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\FilterHelper;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\MailPoetCustomFields;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberDateField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberScore;
|
||||
@ -19,6 +20,7 @@ use MailPoet\Segments\DynamicSegments\Filters\SubscriberTextField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSubscription;
|
||||
@ -33,12 +35,12 @@ class FilterDataMapperTest extends \MailPoetUnitTest {
|
||||
$wp = $this->makeEmpty(WPFunctions::class, [
|
||||
'hasFilter' => false,
|
||||
]);
|
||||
|
||||
$filterHelper = $this->getMockBuilder(DateFilterHelper::class)
|
||||
$wcNumberOfReviews = $this->makeEmpty(WooCommerceNumberOfReviews::class);
|
||||
$dateFilterHelper = $this->getMockBuilder(DateFilterHelper::class)
|
||||
->setMethodsExcept(['getAbsoluteDateOperators', 'getRelativeDateOperators', 'getValidOperators'])
|
||||
->getMock();
|
||||
|
||||
$this->mapper = new FilterDataMapper($wp, $filterHelper);
|
||||
$filterHelper = $this->makeEmptyExcept(FilterHelper::class, 'validateDaysPeriodData');
|
||||
$this->mapper = new FilterDataMapper($wp, $dateFilterHelper, $filterHelper, $wcNumberOfReviews);
|
||||
}
|
||||
|
||||
public function testItChecksFiltersArePresent(): void {
|
||||
@ -885,6 +887,35 @@ class FilterDataMapperTest extends \MailPoetUnitTest {
|
||||
]]]);
|
||||
}
|
||||
|
||||
public function testItMapsWooNumberOfReviews(): void {
|
||||
$data = ['filters' => [[
|
||||
'segmentType' => DynamicSegmentFilterData::TYPE_WOOCOMMERCE,
|
||||
'action' => 'numberOfReviews',
|
||||
'operator' => 'all',
|
||||
'count_type' => '!=',
|
||||
'rating' => '3',
|
||||
'days' => '10',
|
||||
'count' => '3',
|
||||
'timeframe' => 'inTheLast',
|
||||
]]];
|
||||
$filters = $this->mapper->map($data);
|
||||
expect($filters)->array();
|
||||
expect($filters)->count(1);
|
||||
$filter = reset($filters);
|
||||
$this->assertInstanceOf(DynamicSegmentFilterData::class, $filter);
|
||||
expect($filter)->isInstanceOf(DynamicSegmentFilterData::class);
|
||||
expect($filter->getFilterType())->equals(DynamicSegmentFilterData::TYPE_WOOCOMMERCE);
|
||||
expect($filter->getAction())->equals('numberOfReviews');
|
||||
expect($filter->getData())->equals([
|
||||
'connect' => 'and',
|
||||
'days' => '10',
|
||||
'count_type' => '!=',
|
||||
'count' => '3',
|
||||
'rating' => '3',
|
||||
'timeframe' => 'inTheLast',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dateFieldActions
|
||||
*/
|
||||
|
Reference in New Issue
Block a user