Add backend logic for number of reviews filter

MAILPOET-5413
This commit is contained in:
John Oleksowicz
2023-07-14 15:06:52 -05:00
committed by Aschepikov
parent c3bb4d1433
commit ce9cbdc45d
8 changed files with 489 additions and 27 deletions

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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 = [];
}
}

View File

@ -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");
}
}

View File

@ -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
*/