diff --git a/lib/API/JSON/v1/DynamicSegments.php b/lib/API/JSON/v1/DynamicSegments.php index 657ce3fdb1..59ad8d3a35 100644 --- a/lib/API/JSON/v1/DynamicSegments.php +++ b/lib/API/JSON/v1/DynamicSegments.php @@ -150,6 +150,8 @@ class DynamicSegments extends APIEndpoint { return WPFunctions::get()->__('Please select a type for the comparison, an amount and a number of days.', 'mailpoet'); case InvalidFilterException::MISSING_FILTER: return WPFunctions::get()->__('Please add at least one condition for filtering.', 'mailpoet'); + case InvalidFilterException::MISSING_OPERATOR: + return WPFunctions::get()->__('Please select a type for the comparison.', 'mailpoet'); default: return WPFunctions::get()->__('An error occurred while saving data.', 'mailpoet'); } diff --git a/lib/Segments/DynamicSegments/Exceptions/InvalidFilterException.php b/lib/Segments/DynamicSegments/Exceptions/InvalidFilterException.php index 766bb2cb0b..949fb475f5 100644 --- a/lib/Segments/DynamicSegments/Exceptions/InvalidFilterException.php +++ b/lib/Segments/DynamicSegments/Exceptions/InvalidFilterException.php @@ -19,4 +19,5 @@ class InvalidFilterException extends InvalidStateException { const INVALID_DATE_VALUE = 12; const MISSING_COUNTRY = 13; const MISSING_FILTER = 14; + const MISSING_OPERATOR = 15; }; diff --git a/lib/Segments/DynamicSegments/FilterDataMapper.php b/lib/Segments/DynamicSegments/FilterDataMapper.php index d8d0ca5f4c..2b84418677 100644 --- a/lib/Segments/DynamicSegments/FilterDataMapper.php +++ b/lib/Segments/DynamicSegments/FilterDataMapper.php @@ -171,8 +171,10 @@ class FilterDataMapper { if (!isset($data['category_id'])) throw new InvalidFilterException('Missing category', InvalidFilterException::MISSING_CATEGORY_ID); $filterData['category_id'] = $data['category_id']; } elseif ($data['action'] === WooCommerceProduct::ACTION_PRODUCT) { - if (!isset($data['product_id'])) throw new InvalidFilterException('Missing product', InvalidFilterException::MISSING_PRODUCT_ID); - $filterData['product_id'] = $data['product_id']; + if (!isset($data['product_ids'])) throw new InvalidFilterException('Missing product', InvalidFilterException::MISSING_PRODUCT_ID); + if (!isset($data['operator'])) throw new InvalidFilterException('Missing operator', InvalidFilterException::MISSING_OPERATOR); + $filterData['operator'] = $data['operator']; + $filterData['product_ids'] = $data['product_ids']; } elseif ($data['action'] === WooCommerceCountry::ACTION_CUSTOMER_COUNTRY) { if (!isset($data['country_code'])) throw new InvalidFilterException('Missing country', InvalidFilterException::MISSING_COUNTRY); $filterData['country_code'] = $data['country_code']; diff --git a/lib/Segments/DynamicSegments/Filters/WooCommerceProduct.php b/lib/Segments/DynamicSegments/Filters/WooCommerceProduct.php index e6af98ca30..32217718ec 100644 --- a/lib/Segments/DynamicSegments/Filters/WooCommerceProduct.php +++ b/lib/Segments/DynamicSegments/Filters/WooCommerceProduct.php @@ -1,10 +1,12 @@ -getFilterData(); - $productId = (int)$filterData->getParam('product_id'); + $operator = $filterData->getOperator(); + $productIds = $filterData->getParam('product_ids'); $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName(); $parameterSuffix = $filter->getId() ?? Security::generateRandomString(); + $completedOrder = "postmeta.post_id NOT IN ( SELECT id FROM {$wpdb->posts} AS p WHERE p.post_status IN ('wc-cancelled', 'wc-failed'))"; + + if ($operator === DynamicSegmentFilterData::OPERATOR_ANY) { + $this->applyPostmetaJoin($queryBuilder); + $this->applyOrderItemsJoin($queryBuilder); + $this->applyOrderItemmetaJoin($queryBuilder); + $queryBuilder->where("itemmeta.meta_value IN (:products_{$parameterSuffix})"); + + } elseif ($operator === DynamicSegmentFilterData::OPERATOR_ALL) { + $this->applyPostmetaJoin($queryBuilder); + $this->applyOrderItemsJoin($queryBuilder); + $this->applyOrderItemmetaJoin($queryBuilder); + $queryBuilder->where("itemmeta.meta_value IN (:products_{$parameterSuffix})") + ->groupBy("{$subscribersTable}.id, items.order_id") + ->having('COUNT(items.order_id) = :count') + ->setParameter('count', count($productIds)); + + } elseif ($operator === DynamicSegmentFilterData::OPERATOR_NONE) { + $this->applyPostmetaJoin($queryBuilder); + $this->applyOrderItemsJoin($queryBuilder); + // subQuery with subscriber ids that bought products + $subQuery = $this->createQueryBuilder($subscribersTable); + $subQuery->select("DISTINCT $subscribersTable.id"); + $subQuery = $this->applyPostmetaJoin($subQuery); + $subQuery = $this->applyOrderItemsJoin($subQuery); + $subQuery = $this->applyOrderItemmetaJoin($subQuery); + $subQuery->where("itemmeta.meta_value IN (:products_{$parameterSuffix})") + ->andWhere($completedOrder); + // application subQuery for negation + $queryBuilder->where("{$subscribersTable}.id NOT IN ({$subQuery->getSQL()})"); + } + return $queryBuilder + ->andWhere($completedOrder) + ->setParameter("products_{$parameterSuffix}", $productIds, Connection::PARAM_STR_ARRAY); + } + + private function applyPostmetaJoin(QueryBuilder $queryBuilder): QueryBuilder { + global $wpdb; + $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName(); return $queryBuilder->innerJoin( $subscribersTable, $wpdb->postmeta, 'postmeta', "postmeta.meta_key = '_customer_user' AND $subscribersTable.wp_user_id=postmeta.meta_value" - )->join('postmeta', + ); + } + + private function applyOrderItemsJoin(QueryBuilder $queryBuilder): QueryBuilder { + global $wpdb; + return $queryBuilder->join('postmeta', $wpdb->prefix . 'woocommerce_order_items', 'items', 'postmeta.post_id = items.order_id' - )->innerJoin( + ); + } + + private function applyOrderItemmetaJoin(QueryBuilder $queryBuilder): QueryBuilder { + global $wpdb; + return $queryBuilder->innerJoin( 'items', $wpdb->prefix . 'woocommerce_order_itemmeta', 'itemmeta', - "itemmeta.order_item_id=items.order_item_id AND itemmeta.meta_key='_product_id' AND itemmeta.meta_value=:product" . $parameterSuffix - )->andWhere('postmeta.post_id NOT IN ( SELECT id FROM ' . $wpdb->posts . ' as p WHERE p.post_status IN ("wc-cancelled", "wc-failed"))' - )->setParameter('product' . $parameterSuffix, $productId); + "itemmeta.order_item_id=items.order_item_id AND itemmeta.meta_key='_product_id'" + ); + } + + private function createQueryBuilder(string $table): QueryBuilder { + return $this->entityManager->getConnection() + ->createQueryBuilder() + ->from($table); } }