Add support for local variation attributes
In WooCommerce it's possible to create attributes that are local to a specific product. When these attributes are used to generate variations, they are stored in the postmeta table. MAILPOET-5467
This commit is contained in:
committed by
Aschepikov
parent
24e38d6752
commit
a4a2ef3b1f
@@ -9,6 +9,7 @@ import {
|
|||||||
AnyValueTypes,
|
AnyValueTypes,
|
||||||
FilterProps,
|
FilterProps,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
|
WindowLocalProductAttributes,
|
||||||
WindowProductAttributes,
|
WindowProductAttributes,
|
||||||
WooCommerceFormItem,
|
WooCommerceFormItem,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
@@ -18,9 +19,15 @@ export function validatePurchasedWithAttribute(
|
|||||||
): boolean {
|
): boolean {
|
||||||
const purchasedProductWithAttributeIsInvalid =
|
const purchasedProductWithAttributeIsInvalid =
|
||||||
!formItems.operator ||
|
!formItems.operator ||
|
||||||
formItems.attribute_taxonomy_slug === undefined ||
|
(formItems.attribute_type === 'taxonomy' &&
|
||||||
!Array.isArray(formItems.attribute_term_ids) === undefined ||
|
(formItems.attribute_taxonomy_slug === undefined ||
|
||||||
formItems.attribute_term_ids.length === 0;
|
!Array.isArray(formItems.attribute_term_ids) ||
|
||||||
|
formItems.attribute_term_ids.length === 0)) ||
|
||||||
|
(formItems.attribute_type === 'local' &&
|
||||||
|
(!formItems.attribute_local_name ||
|
||||||
|
formItems.attribute_local_name.length === 0 ||
|
||||||
|
!Array.isArray(formItems.attribute_local_values) ||
|
||||||
|
formItems.attribute_local_values.length === 0));
|
||||||
|
|
||||||
return !purchasedProductWithAttributeIsInvalid;
|
return !purchasedProductWithAttributeIsInvalid;
|
||||||
}
|
}
|
||||||
@@ -46,21 +53,61 @@ export function PurchasedWithAttributeFields({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const localProductAttributes: WindowLocalProductAttributes = useSelect(
|
||||||
|
(select) => select(storeName).getLocalProductAttributes(),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const localAttributeOptions = Object.values(localProductAttributes).map(
|
||||||
|
(attribute) => ({
|
||||||
|
// Appending @local to avoid conflicts between taxonomy and local attributes with the same name
|
||||||
|
value: `${attribute.name}@local`,
|
||||||
|
label: attribute.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const localAttributeValues = Object.values(localAttributeOptions).map(
|
||||||
|
(option) => option.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const combinedOptions = [
|
||||||
|
...productAttributesOptions,
|
||||||
|
...localAttributeOptions,
|
||||||
|
];
|
||||||
|
|
||||||
const productAttributeTermsOptionsRef = useRef(null);
|
const productAttributeTermsOptionsRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (segment.attribute_taxonomy_slug === undefined) {
|
if (
|
||||||
|
segment.attribute_taxonomy_slug === undefined &&
|
||||||
|
segment.attribute_local_name === undefined
|
||||||
|
) {
|
||||||
productAttributeTermsOptionsRef.current = null;
|
productAttributeTermsOptionsRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
productAttributeTermsOptionsRef.current = productAttributes[
|
if (segment.attribute_type === 'taxonomy') {
|
||||||
segment.attribute_taxonomy_slug
|
productAttributeTermsOptionsRef.current = productAttributes[
|
||||||
].terms.map((term) => ({
|
segment.attribute_taxonomy_slug
|
||||||
value: term.term_id.toString(),
|
].terms.map((term) => ({
|
||||||
label: term.name,
|
value: term.term_id.toString(),
|
||||||
}));
|
label: term.name,
|
||||||
}, [segment.attribute_taxonomy_slug, productAttributes]);
|
}));
|
||||||
|
} else if (segment.attribute_type === 'local') {
|
||||||
|
productAttributeTermsOptionsRef.current = localProductAttributes[
|
||||||
|
segment.attribute_local_name
|
||||||
|
].values.map((value) => ({
|
||||||
|
value,
|
||||||
|
label: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
segment.attribute_taxonomy_slug,
|
||||||
|
segment.attribute_type,
|
||||||
|
segment.attribute_local_name,
|
||||||
|
productAttributes,
|
||||||
|
localProductAttributes,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -90,23 +137,52 @@ export function PurchasedWithAttributeFields({
|
|||||||
dimension="small"
|
dimension="small"
|
||||||
key="select-segment-product-attribute"
|
key="select-segment-product-attribute"
|
||||||
placeholder={__('Search attributes', 'mailpoet')}
|
placeholder={__('Search attributes', 'mailpoet')}
|
||||||
options={productAttributesOptions}
|
options={combinedOptions}
|
||||||
value={filter((productAttributeOption) => {
|
value={
|
||||||
if (segment.attribute_taxonomy_slug === undefined) {
|
segment.attribute_type === 'local'
|
||||||
return undefined;
|
? filter((localAttributeOption) => {
|
||||||
}
|
if (!segment.attribute_local_name) {
|
||||||
return (
|
return undefined;
|
||||||
segment.attribute_taxonomy_slug === productAttributeOption.value
|
}
|
||||||
);
|
return (
|
||||||
}, productAttributesOptions)}
|
`${segment.attribute_local_name}@local` ===
|
||||||
|
localAttributeOption.value
|
||||||
|
);
|
||||||
|
}, localAttributeOptions)
|
||||||
|
: filter((productAttributeOption) => {
|
||||||
|
if (segment.attribute_taxonomy_slug === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
segment.attribute_taxonomy_slug ===
|
||||||
|
productAttributeOption.value
|
||||||
|
);
|
||||||
|
}, productAttributesOptions)
|
||||||
|
}
|
||||||
onChange={(option: SelectOption): void => {
|
onChange={(option: SelectOption): void => {
|
||||||
void updateSegmentFilter(
|
if (localAttributeValues.includes(option.value)) {
|
||||||
{
|
void updateSegmentFilter(
|
||||||
attribute_taxonomy_slug: option.value,
|
{
|
||||||
attribute_term_ids: [],
|
attribute_type: 'local',
|
||||||
},
|
attribute_local_name: option.value.replace(/@local$/, ''),
|
||||||
filterIndex,
|
attribute_local_values: [],
|
||||||
);
|
attribute_taxonomy_slug: null,
|
||||||
|
attribute_term_ids: null,
|
||||||
|
},
|
||||||
|
filterIndex,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
void updateSegmentFilter(
|
||||||
|
{
|
||||||
|
attribute_type: 'taxonomy',
|
||||||
|
attribute_local_name: null,
|
||||||
|
attribute_local_values: null,
|
||||||
|
attribute_taxonomy_slug: option.value,
|
||||||
|
attribute_term_ids: [],
|
||||||
|
},
|
||||||
|
filterIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{productAttributeTermsOptionsRef.current && (
|
{productAttributeTermsOptionsRef.current && (
|
||||||
@@ -118,26 +194,46 @@ export function PurchasedWithAttributeFields({
|
|||||||
options={productAttributeTermsOptionsRef.current}
|
options={productAttributeTermsOptionsRef.current}
|
||||||
value={filter(
|
value={filter(
|
||||||
(productAttributeTermOption: { value: string; label: string }) => {
|
(productAttributeTermOption: { value: string; label: string }) => {
|
||||||
if (segment.attribute_term_ids === undefined) {
|
if (segment.attribute_local_values) {
|
||||||
return undefined;
|
return (
|
||||||
|
segment.attribute_local_values.indexOf(
|
||||||
|
productAttributeTermOption.value,
|
||||||
|
) !== -1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
if (segment.attribute_term_ids) {
|
||||||
segment.attribute_term_ids.indexOf(
|
return (
|
||||||
productAttributeTermOption.value,
|
segment.attribute_term_ids.indexOf(
|
||||||
) !== -1
|
productAttributeTermOption.value,
|
||||||
);
|
) !== -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
productAttributeTermsOptionsRef.current,
|
productAttributeTermsOptionsRef.current,
|
||||||
)}
|
)}
|
||||||
onChange={(options: SelectOption[]): void => {
|
onChange={(options: SelectOption[]): void => {
|
||||||
void updateSegmentFilter(
|
if (segment.attribute_type === 'local') {
|
||||||
{
|
void updateSegmentFilter(
|
||||||
attribute_term_ids: (options || []).map(
|
{
|
||||||
(x: SelectOption) => x.value,
|
attribute_term_ids: null,
|
||||||
),
|
attribute_local_values: (options || []).map(
|
||||||
},
|
(x: SelectOption) => x.value,
|
||||||
filterIndex,
|
),
|
||||||
);
|
},
|
||||||
|
filterIndex,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
void updateSegmentFilter(
|
||||||
|
{
|
||||||
|
attribute_term_ids: (options || []).map(
|
||||||
|
(x: SelectOption) => x.value,
|
||||||
|
),
|
||||||
|
attribute_local_values: null,
|
||||||
|
},
|
||||||
|
filterIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -28,6 +28,7 @@ export const getInitialState = (): StateType => ({
|
|||||||
membershipPlans: window.mailpoet_membership_plans,
|
membershipPlans: window.mailpoet_membership_plans,
|
||||||
subscriptionProducts: window.mailpoet_subscription_products,
|
subscriptionProducts: window.mailpoet_subscription_products,
|
||||||
productAttributes: window.mailpoet_product_attributes,
|
productAttributes: window.mailpoet_product_attributes,
|
||||||
|
localProductAttributes: window.mailpoet_local_product_attributes,
|
||||||
productCategories: window.mailpoet_product_categories,
|
productCategories: window.mailpoet_product_categories,
|
||||||
newslettersList: window.mailpoet_newsletters_list,
|
newslettersList: window.mailpoet_newsletters_list,
|
||||||
wordpressRoles: window.wordpress_editable_roles_list,
|
wordpressRoles: window.wordpress_editable_roles_list,
|
||||||
|
@@ -12,6 +12,7 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
WindowCustomFields,
|
WindowCustomFields,
|
||||||
WindowEditableRoles,
|
WindowEditableRoles,
|
||||||
|
WindowLocalProductAttributes,
|
||||||
WindowMembershipPlans,
|
WindowMembershipPlans,
|
||||||
WindowNewslettersList,
|
WindowNewslettersList,
|
||||||
WindowProductAttributes,
|
WindowProductAttributes,
|
||||||
@@ -34,6 +35,9 @@ export const getWordpressRoles = (state: StateType): WindowEditableRoles =>
|
|||||||
export const getProductAttributes = (
|
export const getProductAttributes = (
|
||||||
state: StateType,
|
state: StateType,
|
||||||
): WindowProductAttributes => state.productAttributes;
|
): WindowProductAttributes => state.productAttributes;
|
||||||
|
export const getLocalProductAttributes = (
|
||||||
|
state: StateType,
|
||||||
|
): WindowLocalProductAttributes => state.localProductAttributes;
|
||||||
export const getProductCategories = (
|
export const getProductCategories = (
|
||||||
state: StateType,
|
state: StateType,
|
||||||
): WindowProductCategories => state.productCategories;
|
): WindowProductCategories => state.productCategories;
|
||||||
|
@@ -152,8 +152,11 @@ export interface WooCommerceFormItem extends FormItem {
|
|||||||
count?: string;
|
count?: string;
|
||||||
days?: string;
|
days?: string;
|
||||||
coupon_code_ids?: string[];
|
coupon_code_ids?: string[];
|
||||||
|
attribute_type?: 'local' | 'taxonomy';
|
||||||
attribute_taxonomy_slug?: string;
|
attribute_taxonomy_slug?: string;
|
||||||
attribute_term_ids?: string[];
|
attribute_term_ids?: string[];
|
||||||
|
attribute_local_name?: string;
|
||||||
|
attribute_local_values?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationsFormItem extends FormItem {
|
export interface AutomationsFormItem extends FormItem {
|
||||||
@@ -235,6 +238,11 @@ export type WindowProductAttributes = {
|
|||||||
terms: [];
|
terms: [];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export type WindowLocalProductAttributes = {
|
||||||
|
name: string;
|
||||||
|
values: string[];
|
||||||
|
}[];
|
||||||
|
|
||||||
export type WindowProductCategories = {
|
export type WindowProductCategories = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -274,6 +282,7 @@ export interface SegmentFormDataWindow extends Window {
|
|||||||
mailpoet_membership_plans: WindowMembershipPlans;
|
mailpoet_membership_plans: WindowMembershipPlans;
|
||||||
mailpoet_subscription_products: WindowSubscriptionProducts;
|
mailpoet_subscription_products: WindowSubscriptionProducts;
|
||||||
mailpoet_product_attributes: WindowProductAttributes;
|
mailpoet_product_attributes: WindowProductAttributes;
|
||||||
|
mailpoet_local_product_attributes: WindowLocalProductAttributes;
|
||||||
mailpoet_product_categories: WindowProductCategories;
|
mailpoet_product_categories: WindowProductCategories;
|
||||||
mailpoet_woocommerce_countries: WindowWooCommerceCountries;
|
mailpoet_woocommerce_countries: WindowWooCommerceCountries;
|
||||||
mailpoet_woocommerce_payment_methods: WooPaymentMethod[];
|
mailpoet_woocommerce_payment_methods: WooPaymentMethod[];
|
||||||
@@ -295,6 +304,7 @@ export interface StateType {
|
|||||||
subscriptionProducts: WindowSubscriptionProducts;
|
subscriptionProducts: WindowSubscriptionProducts;
|
||||||
wordpressRoles: WindowEditableRoles;
|
wordpressRoles: WindowEditableRoles;
|
||||||
productAttributes: WindowProductAttributes;
|
productAttributes: WindowProductAttributes;
|
||||||
|
localProductAttributes: WindowLocalProductAttributes;
|
||||||
productCategories: WindowProductCategories;
|
productCategories: WindowProductCategories;
|
||||||
newslettersList: WindowNewslettersList;
|
newslettersList: WindowNewslettersList;
|
||||||
canUseWooMemberships: boolean;
|
canUseWooMemberships: boolean;
|
||||||
|
@@ -148,6 +148,16 @@ class DynamicSegments {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch local attributes used for product variations
|
||||||
|
$data['local_product_attributes'] = [];
|
||||||
|
$localAttributes = $this->getLocalAttributesUsedInProductVariations();
|
||||||
|
foreach ($localAttributes as $localAttribute => $values) {
|
||||||
|
$data['local_product_attributes'][$localAttribute] = [
|
||||||
|
'name' => $localAttribute,
|
||||||
|
'values' => $values,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories();
|
$data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories();
|
||||||
@@ -204,6 +214,35 @@ class DynamicSegments {
|
|||||||
$this->pageRenderer->displayPage('segments/dynamic.html', $data);
|
$this->pageRenderer->displayPage('segments/dynamic.html', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getLocalAttributesUsedInProductVariations(): array {
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
if (!$this->woocommerceHelper->isWooCommerceActive()) {
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT DISTINCT pm.meta_key, pm.meta_value
|
||||||
|
FROM {$wpdb->postmeta} pm
|
||||||
|
INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
|
||||||
|
WHERE pm.meta_key LIKE 'attribute_%'
|
||||||
|
AND p.post_type = 'product_variation'
|
||||||
|
GROUP BY pm.meta_key, pm.meta_value";
|
||||||
|
|
||||||
|
$results = $wpdb->get_results($query, ARRAY_A);
|
||||||
|
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$attribute = substr($result['meta_key'], 10);
|
||||||
|
if (!isset($attributes[$attribute])) {
|
||||||
|
$attributes[$attribute] = [];
|
||||||
|
}
|
||||||
|
$attributes[$attribute][] = $result['meta_value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
private function getNewslettersList(): array {
|
private function getNewslettersList(): array {
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($this->newslettersRepository->getStandardNewsletterList() as $newsletter) {
|
foreach ($this->newslettersRepository->getStandardNewsletterList() as $newsletter) {
|
||||||
|
@@ -519,6 +519,9 @@ class FilterDataMapper {
|
|||||||
$filterData['operator'] = $data['operator'];
|
$filterData['operator'] = $data['operator'];
|
||||||
$filterData['attribute_taxonomy_slug'] = $data['attribute_taxonomy_slug'];
|
$filterData['attribute_taxonomy_slug'] = $data['attribute_taxonomy_slug'];
|
||||||
$filterData['attribute_term_ids'] = $data['attribute_term_ids'];
|
$filterData['attribute_term_ids'] = $data['attribute_term_ids'];
|
||||||
|
$filterData['attribute_type'] = $data['attribute_type'];
|
||||||
|
$filterData['attribute_local_name'] = $data['attribute_local_name'];
|
||||||
|
$filterData['attribute_local_values'] = $data['attribute_local_values'];
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidFilterException("Unknown action " . $data['action'], InvalidFilterException::MISSING_ACTION);
|
throw new InvalidFilterException("Unknown action " . $data['action'], InvalidFilterException::MISSING_ACTION);
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,9 @@ use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
|||||||
class WooCommercePurchasedWithAttribute implements Filter {
|
class WooCommercePurchasedWithAttribute implements Filter {
|
||||||
const ACTION = 'purchasedWithAttribute';
|
const ACTION = 'purchasedWithAttribute';
|
||||||
|
|
||||||
|
const TYPE_LOCAL = 'local';
|
||||||
|
const TYPE_TAXONOMY = 'taxonomy';
|
||||||
|
|
||||||
private WooFilterHelper $wooFilterHelper;
|
private WooFilterHelper $wooFilterHelper;
|
||||||
|
|
||||||
private FilterHelper $filterHelper;
|
private FilterHelper $filterHelper;
|
||||||
@@ -32,34 +35,24 @@ class WooCommercePurchasedWithAttribute implements Filter {
|
|||||||
$filterData = $filter->getFilterData();
|
$filterData = $filter->getFilterData();
|
||||||
$this->validateFilterData((array)$filterData->getData());
|
$this->validateFilterData((array)$filterData->getData());
|
||||||
|
|
||||||
$operator = $filterData->getOperator();
|
$type = $filterData->getStringParam('attribute_type');
|
||||||
$attributeTaxonomySlug = $filterData->getStringParam('attribute_taxonomy_slug');
|
|
||||||
$attributeTermIds = $filterData->getArrayParam('attribute_term_ids');
|
|
||||||
|
|
||||||
if ($operator === DynamicSegmentFilterData::OPERATOR_ANY) {
|
if ($type === self::TYPE_LOCAL) {
|
||||||
$this->applyForAnyOperator($queryBuilder, $attributeTaxonomySlug, $attributeTermIds);
|
$this->applyForLocalAttribute($queryBuilder, $filterData);
|
||||||
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_ALL) {
|
} elseif ($type === self::TYPE_TAXONOMY) {
|
||||||
$this->applyForAnyOperator($queryBuilder, $attributeTaxonomySlug, $attributeTermIds);
|
$this->applyForTaxonomyAttribute($queryBuilder, $filterData);
|
||||||
$countParam = $this->filterHelper->getUniqueParameterName('count');
|
|
||||||
$queryBuilder
|
|
||||||
->groupBy('inner_subscriber_id')
|
|
||||||
->having("COUNT(DISTINCT attribute.term_id) = :$countParam")
|
|
||||||
->setParameter($countParam, count($attributeTermIds));
|
|
||||||
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_NONE) {
|
|
||||||
$subQuery = $this->filterHelper->getNewSubscribersQueryBuilder();
|
|
||||||
$this->applyForAnyOperator($subQuery, $attributeTaxonomySlug, $attributeTermIds);
|
|
||||||
$subscribersTable = $this->filterHelper->getSubscribersTable();
|
|
||||||
$queryBuilder->where("{$subscribersTable}.id NOT IN ({$this->filterHelper->getInterpolatedSQL($subQuery)})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $queryBuilder;
|
return $queryBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applyForAnyOperator(QueryBuilder $queryBuilder, string $attributeTaxonomySlug, array $attributeTermIds): void {
|
private function applyForTaxonomyAnyOperator(QueryBuilder $queryBuilder, DynamicSegmentFilterData $filterData): void {
|
||||||
|
$attributeTaxonomySlug = $filterData->getStringParam('attribute_taxonomy_slug');
|
||||||
|
$attributeTermIds = $filterData->getArrayParam('attribute_term_ids');
|
||||||
$termIdsParam = $this->filterHelper->getUniqueParameterName('attribute_term_ids');
|
$termIdsParam = $this->filterHelper->getUniqueParameterName('attribute_term_ids');
|
||||||
$orderStatsAlias = $this->wooFilterHelper->applyOrderStatusFilter($queryBuilder);
|
$orderStatsAlias = $this->wooFilterHelper->applyOrderStatusFilter($queryBuilder);
|
||||||
$productAlias = $this->applyProductJoin($queryBuilder, $orderStatsAlias);
|
$productAlias = $this->applyProductJoin($queryBuilder, $orderStatsAlias);
|
||||||
$attributeAlias = $this->applyAttributeJoin($queryBuilder, $productAlias, $attributeTaxonomySlug);
|
$attributeAlias = $this->applyTaxonomyAttributeJoin($queryBuilder, $productAlias, $attributeTaxonomySlug);
|
||||||
$queryBuilder->andWhere("$attributeAlias.term_id IN (:$termIdsParam)");
|
$queryBuilder->andWhere("$attributeAlias.term_id IN (:$termIdsParam)");
|
||||||
$queryBuilder->setParameter($termIdsParam, $attributeTermIds, Connection::PARAM_STR_ARRAY);
|
$queryBuilder->setParameter($termIdsParam, $attributeTermIds, Connection::PARAM_STR_ARRAY);
|
||||||
}
|
}
|
||||||
@@ -74,7 +67,7 @@ class WooCommercePurchasedWithAttribute implements Filter {
|
|||||||
return $alias;
|
return $alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applyAttributeJoin(QueryBuilder $queryBuilder, string $productAlias, $taxonomySlug, string $alias = 'attribute'): string {
|
private function applyTaxonomyAttributeJoin(QueryBuilder $queryBuilder, string $productAlias, $taxonomySlug, string $alias = 'attribute'): string {
|
||||||
$queryBuilder->innerJoin(
|
$queryBuilder->innerJoin(
|
||||||
$productAlias,
|
$productAlias,
|
||||||
$this->filterHelper->getPrefixedTable('wc_product_attributes_lookup'),
|
$this->filterHelper->getPrefixedTable('wc_product_attributes_lookup'),
|
||||||
@@ -86,6 +79,12 @@ class WooCommercePurchasedWithAttribute implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getLookupData(DynamicSegmentFilterData $filterData): array {
|
public function getLookupData(DynamicSegmentFilterData $filterData): array {
|
||||||
|
$type = $filterData->getStringParam('attribute_type');
|
||||||
|
|
||||||
|
if ($type !== self::TYPE_TAXONOMY) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$slug = $filterData->getStringParam('attribute_taxonomy_slug');
|
$slug = $filterData->getStringParam('attribute_taxonomy_slug');
|
||||||
|
|
||||||
$lookupData = [
|
$lookupData = [
|
||||||
@@ -117,12 +116,93 @@ class WooCommercePurchasedWithAttribute implements Filter {
|
|||||||
) {
|
) {
|
||||||
throw new InvalidFilterException('Missing operator', InvalidFilterException::MISSING_OPERATOR);
|
throw new InvalidFilterException('Missing operator', InvalidFilterException::MISSING_OPERATOR);
|
||||||
}
|
}
|
||||||
$attribute_taxonomy_slug = $data['attribute_taxonomy_slug'] ?? null;
|
$this->validateAttributeData($data);
|
||||||
if (!is_string($attribute_taxonomy_slug) || strlen($attribute_taxonomy_slug) === 0) {
|
}
|
||||||
throw new InvalidFilterException('Missing attribute', InvalidFilterException::MISSING_VALUE);
|
|
||||||
|
public function validateAttributeData(array $data): void {
|
||||||
|
$type = $data['attribute_type'];
|
||||||
|
|
||||||
|
if (!in_array($type, [self::TYPE_LOCAL, self::TYPE_TAXONOMY], true)) {
|
||||||
|
throw new InvalidFilterException('Invalid attribute type', InvalidFilterException::INVALID_TYPE);
|
||||||
}
|
}
|
||||||
if (!isset($data['attribute_term_ids']) || !is_array($data['attribute_term_ids']) || count($data['attribute_term_ids']) === 0) {
|
|
||||||
throw new InvalidFilterException('Missing attribute terms', InvalidFilterException::MISSING_VALUE);
|
if ($type === self::TYPE_LOCAL) {
|
||||||
|
$name = $data['attribute_local_name'] ?? null;
|
||||||
|
if (!is_string($name) || strlen($name) === 0) {
|
||||||
|
throw new InvalidFilterException('Missing attribute', InvalidFilterException::MISSING_VALUE);
|
||||||
|
}
|
||||||
|
$values = $data['attribute_local_values'] ?? [];
|
||||||
|
if (!is_array($values) || count($values) === 0) {
|
||||||
|
throw new InvalidFilterException('Missing attribute values', InvalidFilterException::MISSING_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === self::TYPE_TAXONOMY) {
|
||||||
|
$attribute_taxonomy_slug = $data['attribute_taxonomy_slug'] ?? null;
|
||||||
|
if (!is_string($attribute_taxonomy_slug) || strlen($attribute_taxonomy_slug) === 0) {
|
||||||
|
throw new InvalidFilterException('Missing attribute', InvalidFilterException::MISSING_VALUE);
|
||||||
|
}
|
||||||
|
if (!isset($data['attribute_term_ids']) || !is_array($data['attribute_term_ids']) || count($data['attribute_term_ids']) === 0) {
|
||||||
|
throw new InvalidFilterException('Missing attribute terms', InvalidFilterException::MISSING_VALUE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function applyForTaxonomyAttribute(QueryBuilder $queryBuilder, DynamicSegmentFilterData $filterData) {
|
||||||
|
$operator = $filterData->getOperator();
|
||||||
|
|
||||||
|
if ($operator === DynamicSegmentFilterData::OPERATOR_ANY) {
|
||||||
|
$this->applyForTaxonomyAnyOperator($queryBuilder, $filterData);
|
||||||
|
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_ALL) {
|
||||||
|
$this->applyForTaxonomyAnyOperator($queryBuilder, $filterData);
|
||||||
|
$countParam = $this->filterHelper->getUniqueParameterName('count');
|
||||||
|
$queryBuilder
|
||||||
|
->groupBy('inner_subscriber_id')
|
||||||
|
->having("COUNT(DISTINCT attribute.term_id) = :$countParam")
|
||||||
|
->setParameter($countParam, count($filterData->getArrayParam('attribute_term_ids')));
|
||||||
|
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_NONE) {
|
||||||
|
$subQuery = $this->filterHelper->getNewSubscribersQueryBuilder();
|
||||||
|
$this->applyForTaxonomyAnyOperator($subQuery, $filterData);
|
||||||
|
$subscribersTable = $this->filterHelper->getSubscribersTable();
|
||||||
|
$queryBuilder->where("{$subscribersTable}.id NOT IN ({$this->filterHelper->getInterpolatedSQL($subQuery)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyForLocalAttribute(QueryBuilder $queryBuilder, DynamicSegmentFilterData $filterData): void {
|
||||||
|
$operator = $filterData->getOperator();
|
||||||
|
if ($operator === DynamicSegmentFilterData::OPERATOR_ANY) {
|
||||||
|
$this->applyForLocalAnyAttribute($queryBuilder, $filterData);
|
||||||
|
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_ALL) {
|
||||||
|
$this->applyForLocalAnyAttribute($queryBuilder, $filterData);
|
||||||
|
$countParam = $this->filterHelper->getUniqueParameterName('count');
|
||||||
|
$queryBuilder
|
||||||
|
->groupBy('inner_subscriber_id')
|
||||||
|
->having("COUNT(DISTINCT postmeta.meta_value) = :$countParam")
|
||||||
|
->setParameter($countParam, count($filterData->getArrayParam('attribute_local_values')));
|
||||||
|
} elseif ($operator === DynamicSegmentFilterData::OPERATOR_NONE) {
|
||||||
|
$subQuery = $this->filterHelper->getNewSubscribersQueryBuilder();
|
||||||
|
$this->applyForLocalAnyAttribute($subQuery, $filterData);
|
||||||
|
$subscribersTable = $this->filterHelper->getSubscribersTable();
|
||||||
|
$queryBuilder->where("{$subscribersTable}.id NOT IN ({$this->filterHelper->getInterpolatedSQL($subQuery)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyForLocalAnyAttribute(QueryBuilder $queryBuilder, DynamicSegmentFilterData $filterData): void {
|
||||||
|
$attributeName = $filterData->getStringParam('attribute_local_name');
|
||||||
|
$attributeValues = $filterData->getArrayParam('attribute_local_values');
|
||||||
|
$valuesParam = $this->filterHelper->getUniqueParameterName('attribute_values');
|
||||||
|
$keyParam = $this->filterHelper->getUniqueParameterName('attribute_name');
|
||||||
|
$orderStatsAlias = $this->wooFilterHelper->applyOrderStatusFilter($queryBuilder);
|
||||||
|
$productAlias = $this->applyProductJoin($queryBuilder, $orderStatsAlias);
|
||||||
|
|
||||||
|
$queryBuilder->innerJoin(
|
||||||
|
$productAlias,
|
||||||
|
$this->filterHelper->getPrefixedTable('postmeta'),
|
||||||
|
'postmeta',
|
||||||
|
"$productAlias.product_id = postmeta.post_id AND postmeta.meta_key = :$keyParam AND postmeta.meta_value IN (:$valuesParam)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$queryBuilder->setParameter($keyParam, sprintf("attribute_%s", $attributeName));
|
||||||
|
$queryBuilder->setParameter($valuesParam, $attributeValues, Connection::PARAM_STR_ARRAY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -161,6 +161,12 @@ class IntegrationTester extends \Codeception\Actor {
|
|||||||
do_action('woocommerce_run_product_attribute_lookup_update_callback', $product->get_id(), 1);
|
do_action('woocommerce_run_product_attribute_lookup_update_callback', $product->get_id(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($data['local_attributes'])) {
|
||||||
|
foreach ($data['local_attributes'] as $name => $value) {
|
||||||
|
update_post_meta($product->get_id(), sprintf('attribute_%s', $name), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->wooProductIds[] = $product->get_id();
|
$this->wooProductIds[] = $product->get_id();
|
||||||
return $product;
|
return $product;
|
||||||
}
|
}
|
||||||
|
@@ -11,14 +11,13 @@ use MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchasedWithAttribute;
|
|||||||
*/
|
*/
|
||||||
class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
||||||
|
|
||||||
|
|
||||||
private WooCommercePurchasedWithAttribute $filter;
|
private WooCommercePurchasedWithAttribute $filter;
|
||||||
|
|
||||||
public function _before(): void {
|
public function _before(): void {
|
||||||
$this->filter = $this->diContainer->get(WooCommercePurchasedWithAttribute::class);
|
$this->filter = $this->diContainer->get(WooCommercePurchasedWithAttribute::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItWorksWithAnyOperator(): void {
|
public function testItWorksWithAnyOperatorForTaxonomies(): void {
|
||||||
$product1 = $this->tester->createWooCommerceProduct([
|
$product1 = $this->tester->createWooCommerceProduct([
|
||||||
'price' => 20,
|
'price' => 20,
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
@@ -41,10 +40,10 @@ class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
|||||||
|
|
||||||
$this->createOrder($customer1, [$product1]);
|
$this->createOrder($customer1, [$product1]);
|
||||||
$this->createOrder($customer2, [$product2]);
|
$this->createOrder($customer2, [$product2]);
|
||||||
$this->assertFilterReturnsEmails('any', 'pa_color', [$blueTermId, $redTermId], ['customer1@example.com', 'customer2@example.com']);
|
$this->assertFilterReturnsEmailsForTaxonomyAttributes('any', 'pa_color', [$blueTermId, $redTermId], ['customer1@example.com', 'customer2@example.com']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItWorksWithNoneOperator(): void {
|
public function testItWorksWithNoneOperatorForTaxonomies(): void {
|
||||||
$product1 = $this->tester->createWooCommerceProduct([
|
$product1 = $this->tester->createWooCommerceProduct([
|
||||||
'price' => 20,
|
'price' => 20,
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
@@ -67,10 +66,10 @@ class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
|||||||
|
|
||||||
$this->createOrder($customer1, [$product1]);
|
$this->createOrder($customer1, [$product1]);
|
||||||
$this->createOrder($customer2, [$product2]);
|
$this->createOrder($customer2, [$product2]);
|
||||||
$this->assertFilterReturnsEmails('none', 'pa_color', [$blueTermId, $redTermId], ['customer3@example.com']);
|
$this->assertFilterReturnsEmailsForTaxonomyAttributes('none', 'pa_color', [$blueTermId, $redTermId], ['customer3@example.com']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItWorksWithAllOperator(): void {
|
public function testItWorksWithAllOperatorForTaxonomies(): void {
|
||||||
$product1 = $this->tester->createWooCommerceProduct([
|
$product1 = $this->tester->createWooCommerceProduct([
|
||||||
'price' => 20,
|
'price' => 20,
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
@@ -93,7 +92,88 @@ class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
|||||||
|
|
||||||
$this->createOrder($customer1, [$product1, $product2]);
|
$this->createOrder($customer1, [$product1, $product2]);
|
||||||
$this->createOrder($customer2, [$product2]);
|
$this->createOrder($customer2, [$product2]);
|
||||||
$this->assertFilterReturnsEmails('all', 'pa_color', [$blueTermId, $redTermId], ['customer1@example.com']);
|
$this->assertFilterReturnsEmailsForTaxonomyAttributes('all', 'pa_color', [$blueTermId, $redTermId], ['customer1@example.com']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItWorksWithAnyOperatorForLocalAttributes(): void {
|
||||||
|
$product1 = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'red',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$product2 = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'blue',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$customer1 = $this->tester->createCustomer('customer1@example.com');
|
||||||
|
$customer2 = $this->tester->createCustomer('customer2@example.com');
|
||||||
|
$customer3 = $this->tester->createCustomer('customer3@example.com');
|
||||||
|
|
||||||
|
$this->createOrder($customer1, [$product1]);
|
||||||
|
$this->createOrder($customer2, [$product2]);
|
||||||
|
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('any', 'color', ['red', 'blue'], ['customer1@example.com', 'customer2@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('any', 'color', ['red'], ['customer1@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('any', 'color', ['blue'], ['customer2@example.com']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItWorksWithAllOperatorForLocalAttributes(): void {
|
||||||
|
$product1 = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'red',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$product2 = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'blue',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$customer1 = $this->tester->createCustomer('customer1@example.com');
|
||||||
|
$customer2 = $this->tester->createCustomer('customer2@example.com');
|
||||||
|
$customer3 = $this->tester->createCustomer('customer3@example.com');
|
||||||
|
|
||||||
|
$this->createOrder($customer1, [$product1, $product2]);
|
||||||
|
$this->createOrder($customer2, [$product1]);
|
||||||
|
$this->createOrder($customer3, [$product2]);
|
||||||
|
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('all', 'color', ['red', 'blue'], ['customer1@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('all', 'color', ['red'], ['customer1@example.com', 'customer2@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('all', 'color', ['blue'], ['customer1@example.com', 'customer3@example.com']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItWorksWithNoneOperatorForLocalAttributes(): void {
|
||||||
|
$redProduct = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'red',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$blueProduct = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'blue',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$customer1 = $this->tester->createCustomer('customer1@example.com');
|
||||||
|
$customer2 = $this->tester->createCustomer('customer2@example.com');
|
||||||
|
$customer3 = $this->tester->createCustomer('customer3@example.com');
|
||||||
|
$customer4 = $this->tester->createCustomer('customer4@example.com');
|
||||||
|
|
||||||
|
$this->createOrder($customer1, [$redProduct, $blueProduct]);
|
||||||
|
$this->createOrder($customer2, [$redProduct]);
|
||||||
|
$this->createOrder($customer3, [$blueProduct]);
|
||||||
|
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('none', 'color', ['red', 'blue'], ['customer4@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('none', 'color', ['red'], ['customer3@example.com', 'customer4@example.com']);
|
||||||
|
$this->assertFilterReturnsEmailsForLocalAttributes('none', 'color', ['blue'], ['customer2@example.com', 'customer4@example.com']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItRetrievesLookupData(): void {
|
public function testItRetrievesLookupData(): void {
|
||||||
@@ -117,6 +197,7 @@ class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
|||||||
'operator' => 'any',
|
'operator' => 'any',
|
||||||
'attribute_taxonomy_slug' => 'pa_color',
|
'attribute_taxonomy_slug' => 'pa_color',
|
||||||
'attribute_term_ids' => [$blueTermId, $redTermId],
|
'attribute_term_ids' => [$blueTermId, $redTermId],
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$lookupData = $this->filter->getLookupData($filterData);
|
$lookupData = $this->filter->getLookupData($filterData);
|
||||||
@@ -127,32 +208,142 @@ class WooCommercePurchasedWithAttributeTest extends \MailPoetTest {
|
|||||||
], $lookupData);
|
], $lookupData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItValidatesOperator(): void {
|
public function testItDoesNotGenerateLookupDataForLocalAttributes(): void {
|
||||||
$this->expectException(InvalidFilterException::class);
|
$redProduct = $this->tester->createWooCommerceProduct([
|
||||||
$this->expectExceptionMessage('Missing operator');
|
'price' => 20,
|
||||||
$this->expectExceptionCode(InvalidFilterException::MISSING_OPERATOR);
|
'local_attributes' => [
|
||||||
$this->filter->validateFilterData(['operator' => '', 'attribute_taxonomy_slug' => 'pa_color', 'attribute_term_ids' => ['1']]);
|
'color' => 'red',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$blueProduct = $this->tester->createWooCommerceProduct([
|
||||||
|
'price' => 20,
|
||||||
|
'local_attributes' => [
|
||||||
|
'color' => 'blue',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$filterData = new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchasedWithAttribute::ACTION, [
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_local_name' => 'color',
|
||||||
|
'attribute_local_values' => ['red', 'blue'],
|
||||||
|
'attribute_type' => 'local',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$lookupData = $this->filter->getLookupData($filterData);
|
||||||
|
$this->assertSame([], $lookupData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItValidatesAttribute(): void {
|
/**
|
||||||
$this->expectException(InvalidFilterException::class);
|
* @dataProvider filterDataProvider
|
||||||
$this->expectExceptionMessage('Missing attribute');
|
*/
|
||||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
public function testItValidatesFilterData(array $data, bool $isValid): void {
|
||||||
$this->filter->validateFilterData(['operator' => 'any', 'attribute_taxonomy_slug' => '', 'attribute_term_ids' => ['1']]);
|
if (!$isValid) {
|
||||||
|
$this->expectException(InvalidFilterException::class);
|
||||||
|
}
|
||||||
|
$this->filter->validateFilterData($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItValidatesTerms(): void {
|
public function filterDataProvider(): array {
|
||||||
$this->expectException(InvalidFilterException::class);
|
return [
|
||||||
$this->expectExceptionMessage('Missing attribute terms');
|
'missing term ids' =>
|
||||||
$this->expectExceptionCode(InvalidFilterException::MISSING_VALUE);
|
[
|
||||||
$this->filter->validateFilterData(['operator' => 'any', 'attribute_taxonomy_slug' => 'pa_color', 'attribute_term_ids' => []]);
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
'attribute_taxonomy_slug' => 'pa_color',
|
||||||
|
'attribute_term_ids' => [],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'missing taxonomy slug' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
'attribute_taxonomy_slug' => '',
|
||||||
|
'attribute_term_ids' => ['1'],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'valid taxonomy' => [
|
||||||
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
'attribute_taxonomy_slug' => 'pa_something',
|
||||||
|
'attribute_term_ids' => ['1'],
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
'missing operator' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => '',
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
'attribute_taxonomy_slug' => 'pa_color',
|
||||||
|
'attribute_term_ids' => ['1'],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'invalid operator' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => 'anyyyyy',
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
'attribute_taxonomy_slug' => 'pa_color',
|
||||||
|
'attribute_term_ids' => ['1'],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'missing name' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'local',
|
||||||
|
'attribute_local_name' => '',
|
||||||
|
'attribute_local_values' => ['1'],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'missing values' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'local',
|
||||||
|
'attribute_local_name' => 'color',
|
||||||
|
'attribute_local_values' => [],
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'valid local' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'operator' => 'any',
|
||||||
|
'attribute_type' => 'local',
|
||||||
|
'attribute_local_name' => 'color',
|
||||||
|
'attribute_local_values' => ['red'],
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function assertFilterReturnsEmails(string $operator, string $attributeTaxonomySlug, array $termIds, array $expectedEmails): void {
|
private function assertFilterReturnsEmailsForTaxonomyAttributes(string $operator, string $attributeTaxonomySlug, array $termIds, array $expectedEmails): void {
|
||||||
$filterData = new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchasedWithAttribute::ACTION, [
|
$filterData = new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchasedWithAttribute::ACTION, [
|
||||||
'operator' => $operator,
|
'operator' => $operator,
|
||||||
'attribute_taxonomy_slug' => $attributeTaxonomySlug,
|
'attribute_taxonomy_slug' => $attributeTaxonomySlug,
|
||||||
'attribute_term_ids' => $termIds,
|
'attribute_term_ids' => $termIds,
|
||||||
|
'attribute_type' => 'taxonomy',
|
||||||
|
]);
|
||||||
|
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($filterData, $this->filter);
|
||||||
|
$this->assertEqualsCanonicalizing($expectedEmails, $emails);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertFilterReturnsEmailsForLocalAttributes(string $operator, string $localAttributeName, array $localAttributeValues, array $expectedEmails): void {
|
||||||
|
$filterData = new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchasedWithAttribute::ACTION, [
|
||||||
|
'operator' => $operator,
|
||||||
|
'attribute_local_name' => $localAttributeName,
|
||||||
|
'attribute_local_values' => $localAttributeValues,
|
||||||
|
'attribute_type' => 'local',
|
||||||
]);
|
]);
|
||||||
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($filterData, $this->filter);
|
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($filterData, $this->filter);
|
||||||
$this->assertEqualsCanonicalizing($expectedEmails, $emails);
|
$this->assertEqualsCanonicalizing($expectedEmails, $emails);
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
var wordpress_editable_roles_list = <%= json_encode(wordpress_editable_roles_list) %>;
|
var wordpress_editable_roles_list = <%= json_encode(wordpress_editable_roles_list) %>;
|
||||||
var mailpoet_newsletters_list = <%= json_encode(newsletters_list) %>;
|
var mailpoet_newsletters_list = <%= json_encode(newsletters_list) %>;
|
||||||
var mailpoet_product_attributes = <%= json_encode(product_attributes) %>;
|
var mailpoet_product_attributes = <%= json_encode(product_attributes) %>;
|
||||||
|
var mailpoet_local_product_attributes = <%= json_encode(local_product_attributes) %>;
|
||||||
var mailpoet_product_categories = <%= json_encode(product_categories) %>;
|
var mailpoet_product_categories = <%= json_encode(product_categories) %>;
|
||||||
var mailpoet_products = <%= json_encode(products) %>;
|
var mailpoet_products = <%= json_encode(products) %>;
|
||||||
var mailpoet_membership_plans = <%= json_encode(membership_plans) %>;
|
var mailpoet_membership_plans = <%= json_encode(membership_plans) %>;
|
||||||
|
Reference in New Issue
Block a user