2019-03-31 00:21:31 -04:00
<? php
/**
* ConnectionResolver - Product_Connection_Resolver
*
* Resolves connections to Products
*
2019-10-25 19:13:36 -04:00
* @package WPGraphQL\WooCommerce\Data\Connection
2019-03-31 00:21:31 -04:00
* @since 0.0.1
*/
2019-10-25 19:13:36 -04:00
namespace WPGraphQL\WooCommerce\Data\Connection ;
2019-03-31 00:21:31 -04:00
2024-08-06 18:07:37 -04:00
use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery ;
2019-03-31 00:21:31 -04:00
use WPGraphQL\Data\Connection\AbstractConnectionResolver ;
2023-08-22 17:14:39 -04:00
use WPGraphQL\Utils\Utils ;
2023-07-19 23:01:50 +03:00
use WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce ;
2019-03-31 00:21:31 -04:00
/**
* Class Product_Connection_Resolver
2021-05-15 17:56:16 -04:00
*
2023-07-19 23:01:50 +03:00
* @property \WPGraphQL\WooCommerce\Data\Loader\WC_CPT_Loader $loader
2019-03-31 00:21:31 -04:00
*/
class Product_Connection_Resolver extends AbstractConnectionResolver {
2020-01-13 14:24:11 -05:00
/**
2020-03-27 12:18:06 -04:00
* Include CPT Loader connection common functions.
2020-01-13 14:24:11 -05:00
*/
2020-03-27 12:18:06 -04:00
use WC_CPT_Loader_Common ;
2019-04-10 12:26:37 -04:00
/**
* The name of the post type, or array of post types the connection resolver is resolving for
*
2023-08-22 17:14:39 -04:00
* @var string[]
2019-04-10 12:26:37 -04:00
*/
protected $post_type ;
2024-05-07 16:50:42 -04:00
/**
* The instance of the class that helps filtering with the product attributes lookup table.
*
2024-08-06 18:07:37 -04:00
* @var \Automattic\WooCommerce\StoreApi\Utilities\ProductQuery
2024-05-07 16:50:42 -04:00
*/
2024-08-06 18:07:37 -04:00
private $products_query ;
2024-05-07 16:50:42 -04:00
2019-04-10 12:26:37 -04:00
/**
* Refund_Connection_Resolver constructor.
*
2023-07-19 23:01:50 +03:00
* @param mixed $source The object passed down from the previous level in the Resolve tree.
* @param array $args The input arguments for the query.
* @param \WPGraphQL\AppContext $context The context of the request.
* @param \GraphQL\Type\Definition\ResolveInfo $info The resolve info passed down the Resolve tree.
2019-04-10 12:26:37 -04:00
*/
public function __construct ( $source , $args , $context , $info ) {
2019-05-09 13:14:45 -04:00
// @codingStandardsIgnoreLine.
2023-08-22 17:14:39 -04:00
$this -> post_type = [ 'product' ];
2019-04-23 22:26:45 -04:00
2024-08-06 18:07:37 -04:00
$this -> products_query = new ProductQuery ();
2024-05-07 16:50:42 -04:00
2019-04-10 12:26:37 -04:00
/**
* Call the parent construct to setup class data
*/
parent :: __construct ( $source , $args , $context , $info );
}
2020-03-20 22:59:47 -04:00
/**
* Return the name of the loader to be used with the connection resolver
*
* @return string
*/
public function get_loader_name () {
2021-06-03 21:37:09 -04:00
return 'wc_post' ;
2020-03-20 22:59:47 -04:00
}
2019-03-31 00:21:31 -04:00
/**
2020-01-11 19:43:08 -05:00
* Confirms the user has the privileges to query the products
2019-03-31 00:21:31 -04:00
*
* @return bool
*/
public function should_execute () {
return true ;
}
/**
2023-08-22 17:14:39 -04:00
* {@inheritDoc}
2019-03-31 00:21:31 -04:00
*/
public function get_query_args () {
2023-08-22 17:14:39 -04:00
/**
* Prepare for later use
*/
2019-03-31 00:21:31 -04:00
$last = ! empty ( $this -> args [ 'last' ] ) ? $this -> args [ 'last' ] : null ;
$first = ! empty ( $this -> args [ 'first' ] ) ? $this -> args [ 'first' ] : null ;
2023-08-22 17:14:39 -04:00
$query_args = [];
2023-06-13 23:17:02 +03:00
/**
2023-08-22 17:14:39 -04:00
* Ignore sticky posts by default
2023-06-13 23:17:02 +03:00
*/
2023-08-22 17:14:39 -04:00
$query_args [ 'ignore_sticky_posts' ] = true ;
/**
* Set post_type
*/
$query_args [ 'post_type' ] = $this -> post_type ;
2024-05-07 16:50:42 -04:00
/**
* Set the wc_query to product_query
*/
$query_args [ 'wc_query' ] = 'product_query' ;
2023-08-22 17:14:39 -04:00
/**
* Set the post_status
*/
$query_args [ 'post_status' ] = [ 'draft' , 'pending' , 'private' , 'publish' ];
$query_args [ 'perm' ] = 'readable' ;
/**
* Set posts_per_page the highest value of $first and $last, with a (filterable) max of 100
*/
$query_args [ 'posts_per_page' ] = $this -> one_to_one ? 1 : min ( max ( absint ( $first ), absint ( $last ), 10 ), $this -> query_amount ) + 1 ;
/**
* Set the graphql cursor args.
*/
$query_args [ 'graphql_cursor_compare' ] = ( ! empty ( $last ) ) ? '>' : '<' ;
$query_args [ 'graphql_after_cursor' ] = $this -> get_after_offset ();
$query_args [ 'graphql_before_cursor' ] = $this -> get_before_offset ();
/**
* If the cursor offsets not empty,
* ignore sticky posts on the query
2023-09-15 23:31:24 -04:00
* and don't count the total number of posts
2023-08-22 17:14:39 -04:00
*/
2023-09-15 23:31:24 -04:00
if ( ! empty ( $this -> get_after_offset () ) || ! empty ( $this -> get_before_offset () ) ) {
2023-08-22 17:14:39 -04:00
$query_args [ 'ignore_sticky_posts' ] = true ;
2023-09-15 23:31:24 -04:00
$query_args [ 'no_found_rows' ] = true ;
2023-08-22 17:14:39 -04:00
}
/**
* Pass the graphql $args to the WP_Query
*/
$query_args [ 'graphql_args' ] = $this -> args ;
2019-03-31 00:21:31 -04:00
2019-04-08 23:52:45 -04:00
/**
* Collect the input_fields and sanitize them to prepare them for sending to the WP_Query
*/
2022-08-26 14:53:36 -04:00
$input_fields = [];
2019-04-08 23:52:45 -04:00
if ( ! empty ( $this -> args [ 'where' ] ) ) {
$input_fields = $this -> sanitize_input_fields ( $this -> args [ 'where' ] );
}
2023-08-22 17:14:39 -04:00
/**
* Merge the input_fields with the default query_args
*/
2019-04-08 23:52:45 -04:00
if ( ! empty ( $input_fields ) ) {
$query_args = array_merge ( $query_args , $input_fields );
}
2019-09-29 15:11:02 -04:00
/**
2023-08-22 17:14:39 -04:00
* If the query contains search default the results to
2019-09-29 15:11:02 -04:00
*/
2024-08-06 18:07:37 -04:00
if ( ! empty ( $query_args [ 'search' ] ) && empty ( $query_args [ 'orderby' ] ) ) {
2023-08-22 17:14:39 -04:00
/**
* Don't order search results by title (causes funky issues with cursors)
*/
$query_args [ 'search_orderby_title' ] = false ;
2024-05-07 16:50:42 -04:00
$query_args = array_merge ( $query_args , \WC () -> query -> get_catalog_ordering_args ( 'relevance' , isset ( $last ) ? 'ASC' : 'DESC' ) );
2019-04-08 23:52:45 -04:00
}
2019-04-10 12:26:37 -04:00
if ( empty ( $query_args [ 'orderby' ] ) ) {
2024-05-07 16:50:42 -04:00
$query_args = array_merge ( $query_args , \WC () -> query -> get_catalog_ordering_args ( 'menu_order' , isset ( $last ) ? 'ASC' : 'DESC' ) );
2019-04-10 12:26:37 -04:00
}
2024-08-06 18:07:37 -04:00
$has_offset = isset ( $last ) ? $this -> get_before_offset () : $this -> get_after_offset ();
$offset_product = null ;
if ( $has_offset ) {
/** @var \WPGraphQL\WooCommerce\Model\Product|null $offset_model */
$offset_model = $this -> get_loader () -> load ( $has_offset );
/** @var \WC_Product|\WC_Product_Variable|null $offset_product */
$offset_product = $offset_model ? $offset_model -> as_WC_Data () : null ;
}
if ( $offset_product && 'price' === $query_args [ 'orderby' ] ) {
/** @var array<float>|float|null $price */
$price = is_a ( $offset_product , 'WC_Product_Variable' )
? $offset_product -> get_variation_price ()
: $offset_product -> get_price ();
if ( 'ASC' === $query_args [ 'order' ] ) {
if ( is_array ( $price ) ) {
$price = reset ( $price );
}
$query_args [ 'graphql_cursor_compare_by_price_key' ] = 'wc_product_meta_lookup.min_price' ;
$query_args [ 'graphql_cursor_compare_by_price_value' ] = $price ;
} else {
if ( is_array ( $price ) ) {
$price = end ( $price );
}
$query_args [ 'graphql_cursor_compare_by_price_key' ] = 'wc_product_meta_lookup.max_price' ;
$query_args [ 'graphql_cursor_compare_by_price_value' ] = $price ;
}
2025-02-27 10:21:48 -05:00
} elseif ( $offset_product && 'popularity' === $query_args [ 'orderby' ] ) {
2024-08-06 18:07:37 -04:00
$query_args [ 'graphql_cursor_compare_by_popularity_value' ] = $offset_product -> get_total_sales ();
$query_args [ 'graphql_cursor_compare_by_popularity_key' ] = 'wc_product_meta_lookup.total_sales' ;
2025-02-27 10:21:48 -05:00
} elseif ( $offset_product && 'rating' === $query_args [ 'orderby' ] ) {
2024-08-06 18:07:37 -04:00
$query_args [ 'graphql_cursor_compare_by_rating_value' ] = $offset_product -> get_average_rating ();
$query_args [ 'graphql_cursor_compare_by_rating_key' ] = 'wc_product_meta_lookup.average_rating' ;
2025-02-27 10:21:48 -05:00
} elseif ( $offset_product && 'comment_count' === $query_args [ 'orderby' ] ) {
2024-08-06 18:07:37 -04:00
$query_args [ 'graphql_cursor_compare_by_comment_count_value' ] = $offset_product -> get_rating_count ();
$query_args [ 'graphql_cursor_compare_by_comment_count_key' ] = 'wc_product_meta_lookup.rating_count' ;
$query_args [ 'graphql_cursor_compare_by_rating_value' ] = $offset_product -> get_average_rating ();
$query_args [ 'graphql_cursor_compare_by_rating_key' ] = 'wc_product_meta_lookup.average_rating' ;
2025-02-27 10:21:48 -05:00
} elseif ( $offset_product && 'menu_order title' === $query_args [ 'orderby' ] ) {
$query_args [ 'orderby' ] = [
'menu_order' => $query_args [ 'order' ],
'post_title' => isset ( $this -> args [ 'last' ] ) ? 'ASC' : 'DESC' ,
];
unset ( $query_args [ 'order' ] );
2024-08-06 18:07:37 -04:00
}
2019-04-08 23:52:45 -04:00
/**
2023-08-22 17:14:39 -04:00
* NOTE: Only IDs should be queried here as the Deferred resolution will handle
* fetching the full objects, either from cache of from a follow-up query to the DB
2019-04-08 23:52:45 -04:00
*/
2023-08-22 17:14:39 -04:00
$query_args [ 'fields' ] = 'ids' ;
2019-04-08 23:52:45 -04:00
2023-08-22 17:14:39 -04:00
/**
* Filter the $query args to allow folks to customize queries programmatically
*
* @param array $query_args The args that will be passed to the WP_Query
* @param mixed $source The source that's passed down the GraphQL queries
2024-08-06 18:07:37 -04:00
* @param array<string, mixed>|null $args The inputArgs on the field
2023-08-22 17:14:39 -04:00
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
*/
return apply_filters ( 'graphql_product_connection_query_args' , $query_args , $this -> source , $this -> args , $this -> context , $this -> info );
2019-03-31 00:21:31 -04:00
}
/**
2023-08-22 17:14:39 -04:00
* {@inheritDoc}
2019-03-31 00:21:31 -04:00
*/
public function get_query () {
2024-08-06 18:07:37 -04:00
add_filter ( 'posts_clauses' , [ $this -> products_query , 'add_query_clauses' ], 10 , 2 );
2024-05-07 16:50:42 -04:00
2024-11-06 11:47:16 -05:00
// Temporary fix for the search query.
if ( ! empty ( $this -> query_args [ 'search' ] ) ) {
$this -> query_args [ 'fulltext_search' ] = $this -> query_args [ 'search' ];
unset ( $this -> query_args [ 'search' ] );
add_filter ( 'posts_clauses' , [ $this , 'add_search_query_clause' ], 10 , 2 );
}
2024-08-06 18:07:37 -04:00
return new \WP_Query ();
2019-03-31 00:21:31 -04:00
}
/**
2023-08-22 17:14:39 -04:00
* {@inheritDoc}
2019-03-31 00:21:31 -04:00
*/
2023-08-22 17:14:39 -04:00
public function get_ids_from_query () {
2024-08-06 18:07:37 -04:00
// Run query and get IDs.
$ids = $this -> query -> query ( $this -> query_args );
2024-11-06 11:47:16 -05:00
if ( ! empty ( $this -> query_args [ 'fulltext_search' ] ) ) {
remove_filter ( 'posts_clauses' , [ $this , 'add_search_query_clause' ], 10 );
}
2024-08-06 18:07:37 -04:00
remove_filter ( 'posts_clauses' , [ $this -> products_query , 'add_query_clauses' ], 10 );
2023-08-22 17:14:39 -04:00
// If we're going backwards, we need to reverse the array.
if ( ! empty ( $this -> args [ 'last' ] ) ) {
$ids = array_reverse ( $ids );
}
return $ids ;
2019-03-31 00:21:31 -04:00
}
2019-04-08 23:52:45 -04:00
2019-09-29 15:11:02 -04:00
/**
* Returns meta keys to be used for connection ordering.
*
2023-12-27 11:28:24 -05:00
* @param bool $is_numeric Return numeric meta keys. Defaults to "true".
2024-05-16 21:27:54 -04:00
*
2019-09-29 15:11:02 -04:00
* @return array
*/
2023-12-27 11:28:24 -05:00
public function ordering_meta ( $is_numeric = true ) {
if ( ! $is_numeric ) {
return apply_filters (
'woographql_product_connection_orderby_meta_keys' ,
[]
);
}
2023-12-22 18:06:12 -05:00
return apply_filters (
2023-12-27 11:28:24 -05:00
'woographql_product_connection_orderby_numeric_meta_keys' ,
2023-12-22 18:06:12 -05:00
[
'_sale_price_dates_from' ,
'_sale_price_dates_to' ,
'total_sales' ,
]
);
2019-09-29 15:11:02 -04:00
}
2024-11-06 11:47:16 -05:00
/**
* This function replaces the default product query search query clause with a clause searching the product's description, short description and slug.
*
* @param array $args The query arguments.
* @param \WP_Query $wp_query The WP_Query object.
* @return array
*/
public function add_search_query_clause ( $args , $wp_query ) {
global $wpdb ;
if ( empty ( $wp_query -> get ( 'fulltext_search' ) ) ) {
return $args ;
}
$search = '%' . $wpdb -> esc_like ( $wp_query -> get ( 'fulltext_search' ) ) . '%' ;
$search_query = $wpdb -> prepare ( " AND ( $wpdb->posts .post_title LIKE %s OR $wpdb->posts .post_name LIKE %s OR wc_product_meta_lookup.sku LIKE %s OR $wpdb->posts .post_content LIKE %s OR $wpdb->posts .post_excerpt LIKE %s ) " , $search , $search , $search , $search , $search );
$args [ 'where' ] .= $search_query ;
if ( ! strstr ( $args [ 'join' ], 'wc_product_meta_lookup' ) ) {
$args [ 'join' ] .= " LEFT JOIN { $wpdb -> wc_product_meta_lookup } wc_product_meta_lookup ON $wpdb->posts .ID = wc_product_meta_lookup.product_id " ;
}
return $args ;
}
2025-10-11 16:29:36 -04:00
/**
* Process taxonomy filters for taxonomyFilter argument.
*
* @param array $filters Array of taxonomy filters.
* @param string $relation The relation between filters (AND/OR).
*
* @return array
*/
private function process_taxonomy_filters ( array $filters , string $relation ) {
$tax_groups = [];
foreach ( $filters as $filter ) {
$common = [
'taxonomy' => $filter [ 'taxonomy' ],
'operator' => ! empty ( $filter [ 'operator' ] ) ? $filter [ 'operator' ] : 'IN' ,
];
if ( ! empty ( $filter [ 'ids' ] ) ) {
$tax_groups [] = array_merge (
$common ,
[
'field' => 'ID' ,
'terms' => $filter [ 'ids' ],
]
);
}
if ( ! empty ( $filter [ 'terms' ] ) ) {
$tax_groups [] = array_merge (
$common ,
[
'field' => 'slug' ,
'terms' => $filter [ 'terms' ],
]
);
}
} //end foreach
if ( empty ( $tax_groups ) ) {
return [];
}
if ( 1 === count ( $tax_groups ) ) {
return $tax_groups [ 0 ];
}
// Add relation if there are multiple groups.
$tax_groups [ 'relation' ] = $relation ;
return $tax_groups ;
}
2019-04-08 23:52:45 -04:00
/**
* This sets up the "allowed" args, and translates the GraphQL-friendly keys to WP_Query
* friendly keys. There's probably a cleaner/more dynamic way to approach this, but
* this was quick. I'd be down to explore more dynamic ways to map this, but for
* now this gets the job done.
*
* @param array $where_args - arguments being used to filter query.
*
* @return array
*/
public function sanitize_input_fields ( array $where_args ) {
2023-08-22 17:14:39 -04:00
$query_args = Utils :: map_input (
2024-05-07 16:50:42 -04:00
$where_args ,
2023-08-22 17:14:39 -04:00
[
'slugIn' => 'post_name__in' ,
'minPrice' => 'min_price' ,
'maxPrice' => 'max_price' ,
'stockStatus' => 'stock_status' ,
'status' => 'post_status' ,
2024-05-07 16:50:42 -04:00
'include' => 'post__in' ,
'exclude' => 'post__not_in' ,
'parent' => 'post_parent' ,
'parentIn' => 'post_parent__in' ,
'parentNotIn' => 'post_parent__not_in' ,
'search' => 'search' ,
2024-11-06 11:47:16 -05:00
2023-08-22 17:14:39 -04:00
]
);
2019-04-08 23:52:45 -04:00
2024-05-07 16:50:42 -04:00
if ( ! empty ( $where_args [ 'orderby' ] ) ) {
$default_order = isset ( $this -> args [ 'last' ] ) ? 'ASC' : 'DESC' ;
$orderby_input = current ( $where_args [ 'orderby' ] );
2024-08-06 18:07:37 -04:00
$orderby = $orderby_input [ 'field' ];
$order = ! empty ( $orderby_input [ 'order' ] ) ? $orderby_input [ 'order' ] : $default_order ;
// Set the order to DESC if orderby is popularity.
if ( 'popularity' === $orderby || 'rating' === $orderby ) {
$order = 'DESC' ;
}
$query_args = array_merge ( $query_args , \wc () -> query -> get_catalog_ordering_args ( $orderby , $order ) );
2024-05-07 16:50:42 -04:00
}
2023-08-22 17:14:39 -04:00
if ( isset ( $where_args [ 'includeVariations' ] ) && $where_args [ 'includeVariations' ] ) {
$query_args [ 'post_type' ] = [ 'product' , 'product_variation' ];
2019-04-08 23:52:45 -04:00
}
2022-08-26 14:53:36 -04:00
$tax_query = [];
$taxonomy_args = [
2026-03-23 12:28:48 -04:00
'type' => 'product_type' ,
'typeIn' => 'product_type' ,
'typeNotIn' => 'product_type' ,
'category' => 'product_cat' ,
'categoryIn' => 'product_cat' ,
'categoryNotIn' => 'product_cat' ,
'categoryId' => 'product_cat' ,
'categoryIdIn' => 'product_cat' ,
'categoryIdNotIn' => 'product_cat' ,
'tag' => 'product_tag' ,
'tagIn' => 'product_tag' ,
'tagNotIn' => 'product_tag' ,
'tagId' => 'product_tag' ,
'tagIdIn' => 'product_tag' ,
'tagIdNotIn' => 'product_tag' ,
'productBrand' => 'product_brand' ,
'productBrandIn' => 'product_brand' ,
'productBrandNotIn' => 'product_brand' ,
'productBrandId' => 'product_brand' ,
'productBrandIdIn' => 'product_brand' ,
'productBrandIdNotIn' => 'product_brand' ,
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
foreach ( $taxonomy_args as $field => $taxonomy ) {
if ( ! empty ( $where_args [ $field ] ) ) {
2019-08-22 11:27:52 -04:00
// Set tax query operator.
switch ( true ) {
2023-09-11 17:42:26 -04:00
case str_ends_with ( $field , 'NotIn' ) :
2019-08-22 11:27:52 -04:00
$operator = 'NOT IN' ;
break ;
default :
$operator = 'IN' ;
break ;
}
// Set tax query config.
2019-04-08 23:52:45 -04:00
switch ( $field ) {
case 'type' :
2023-08-22 17:14:39 -04:00
// If the type is variation, we only need to set the post_type arg.
if ( 'variation' === $where_args [ $field ] ) {
$query_args [ 'post_type' ] = [ 'product_variation' ];
break ;
}
// Otherwise continue to create a tax query.
2019-04-08 23:52:45 -04:00
case 'typeIn' :
2023-08-22 17:14:39 -04:00
if ( is_array ( $where_args [ $field ] ) && in_array ( 'variation' , $where_args [ $field ], true ) ) {
$query_args [ 'post_type' ] = array_merge ( $this -> post_type , [ 'product_variation' ] );
}
$tax_query [] = [ // phpcs:ignore SlevomatCodingStandard.Arrays.DisallowPartiallyKeyed.DisallowedPartiallyKeyed
'relation' => 'OR' ,
[
'taxonomy' => 'product_type' ,
'field' => 'slug' ,
'terms' => $where_args [ $field ],
],
[
'taxonomy' => 'product_type' ,
'field' => 'id' ,
'operator' => 'NOT EXISTS' ,
],
];
break ;
2019-04-08 23:52:45 -04:00
case 'typeNotIn' :
case 'category' :
case 'categoryIn' :
case 'categoryNotIn' :
case 'tag' :
case 'tagIn' :
case 'tagNotIn' :
2026-03-23 12:28:48 -04:00
case 'productBrand' :
case 'productBrandIn' :
case 'productBrandNotIn' :
2023-12-27 11:29:44 -05:00
// Get terms.
$terms = $where_args [ $field ];
if ( ! is_array ( $terms ) ) {
$terms = [ $terms ];
}
// Get term taxonomy IDs for complex tax queries.
$term_taxonomy_ids = [];
foreach ( $terms as $term_slug ) {
$term = get_term_by ( 'slug' , $term_slug , $taxonomy );
2025-11-14 14:30:01 -05:00
if ( ! $term ) {
2023-12-27 11:29:44 -05:00
continue ;
}
$term_taxonomy_ids [] = $term -> term_taxonomy_id ;
}
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-08-22 11:27:52 -04:00
'taxonomy' => $taxonomy ,
2023-12-27 11:29:44 -05:00
'field' => 'term_taxonomy_id' ,
'terms' => $term_taxonomy_ids ,
2019-08-22 11:27:52 -04:00
'operator' => $operator ,
2022-08-26 14:53:36 -04:00
];
2019-08-22 11:27:52 -04:00
break ;
case 'categoryId' :
case 'categoryIdIn' :
case 'categoryIdNotIn' :
case 'tagId' :
case 'tagIdIn' :
case 'tagIdNotIn' :
2026-03-23 12:28:48 -04:00
case 'productBrandId' :
case 'productBrandIdIn' :
case 'productBrandIdNotIn' :
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-04-08 23:52:45 -04:00
'taxonomy' => $taxonomy ,
'field' => 'term_id' ,
'terms' => $where_args [ $field ],
2019-08-22 11:27:52 -04:00
'operator' => $operator ,
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
break ;
2022-02-04 15:00:44 -05:00
} //end switch
} //end if
} //end foreach
2019-04-08 23:52:45 -04:00
// Filter by attribute and term.
if ( ! empty ( $where_args [ 'attribute' ] ) && ! empty ( $where_args [ 'attributeTerm' ] ) ) {
2024-05-16 21:27:54 -04:00
graphql_debug (
2026-06-30 10:16:21 -04:00
__ ( 'The "attribute" and "attributeTerm" arguments have been deprecated. Please use the "attributes" argument instead.' , 'graphql-for-ecommerce' ),
2024-05-16 21:27:54 -04:00
);
2019-04-08 23:52:45 -04:00
if ( in_array ( $where_args [ 'attribute' ], \wc_get_attribute_taxonomy_names (), true ) ) {
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-04-08 23:52:45 -04:00
'taxonomy' => $where_args [ 'attribute' ],
2019-06-07 14:41:57 -04:00
'field' => 'slug' ,
2019-04-08 23:52:45 -04:00
'terms' => $where_args [ 'attributeTerm' ],
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
}
}
2024-05-16 21:27:54 -04:00
// Filter by attributes.
if ( ! empty ( $where_args [ 'attributes' ] ) && ! empty ( $where_args [ 'attributes' ][ 'queries' ] ) ) {
$attributes = $where_args [ 'attributes' ][ 'queries' ];
$att_queries = [];
foreach ( $attributes as $attribute ) {
if ( empty ( $attribute [ 'ids' ] ) && empty ( $attribute [ 'terms' ] ) ) {
continue ;
}
if ( ! in_array ( $attribute [ 'taxonomy' ], \wc_get_attribute_taxonomy_names (), true ) ) {
continue ;
}
$operator = isset ( $attribute [ 'operator' ] ) ? $attribute [ 'operator' ] : 'IN' ;
if ( ! empty ( $attribute [ 'terms' ] ) ) {
foreach ( $attribute [ 'terms' ] as $term ) {
$att_queries [] = [
'taxonomy' => $attribute [ 'taxonomy' ],
'field' => 'slug' ,
'terms' => $term ,
'operator' => $operator ,
];
}
}
if ( ! empty ( $attribute [ 'ids' ] ) ) {
foreach ( $attribute [ 'ids' ] as $id ) {
$att_queries [] = [
'taxonomy' => $attribute [ 'taxonomy' ],
'field' => 'term_id' ,
'terms' => $id ,
'operator' => $operator ,
];
}
}
}
if ( 1 < count ( $att_queries ) ) {
$relation = ! empty ( $where_args [ 'attributes' ][ 'relation' ] ) ? $where_args [ 'attributes' ][ 'relation' ] : 'AND' ;
if ( 'NOT_IN' === $relation ) {
2026-06-30 10:16:21 -04:00
graphql_debug ( __ ( 'The "NOT_IN" relation is not supported for attributes. Please use "IN" or "AND" instead.' , 'graphql-for-ecommerce' ) );
2024-05-16 21:27:54 -04:00
$relation = 'IN' ;
}
$tax_query [] = array_merge (
[ 'relation' => $relation ],
$att_queries
);
} else {
$tax_query = array_merge ( $tax_query , $att_queries );
}
}
2020-01-03 13:05:58 -05:00
if ( empty ( $where_args [ 'type' ] ) && empty ( $where_args [ 'typeIn' ] ) && ! empty ( $where_args [ 'supportedTypesOnly' ] )
&& true === $where_args [ 'supportedTypesOnly' ] ) {
2023-08-22 17:14:39 -04:00
$supported_types = array_keys ( WP_GraphQL_WooCommerce :: get_enabled_product_types () );
$terms = ! empty ( $where_args [ 'typeNotIn' ] )
? array_diff ( $supported_types , $where_args [ 'typeNotIn' ] )
: $supported_types ;
$tax_query [] = [
2019-04-08 23:52:45 -04:00
'taxonomy' => 'product_type' ,
'field' => 'slug' ,
2020-01-03 13:05:58 -05:00
'terms' => $terms ,
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
}
2019-05-09 13:14:45 -04:00
if ( isset ( $where_args [ 'featured' ] ) ) {
$product_visibility_term_ids = wc_get_product_visibility_term_ids ();
if ( $where_args [ 'featured' ] ) {
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
2022-08-26 14:53:36 -04:00
'terms' => [ $product_visibility_term_ids [ 'featured' ] ],
];
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
2022-08-26 14:53:36 -04:00
'terms' => [ $product_visibility_term_ids [ 'exclude-from-catalog' ] ],
2019-05-09 13:14:45 -04:00
'operator' => 'NOT IN' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
} else {
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
2022-08-26 14:53:36 -04:00
'terms' => [ $product_visibility_term_ids [ 'featured' ] ],
2019-05-09 13:14:45 -04:00
'operator' => 'NOT IN' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
}
2022-02-04 15:00:44 -05:00
} //end if
2019-04-08 23:52:45 -04:00
2019-05-09 13:14:45 -04:00
// Handle visibility.
2019-09-17 12:06:41 -04:00
if ( ! empty ( $where_args [ 'visibility' ] ) ) {
2019-05-09 13:14:45 -04:00
switch ( $where_args [ 'visibility' ] ) {
case 'search' :
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'slug' ,
2022-08-26 14:53:36 -04:00
'terms' => [ 'exclude-from-search' ],
2019-05-09 13:14:45 -04:00
'operator' => 'NOT IN' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
break ;
case 'catalog' :
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'slug' ,
2022-08-26 14:53:36 -04:00
'terms' => [ 'exclude-from-catalog' ],
2019-05-09 13:14:45 -04:00
'operator' => 'NOT IN' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
break ;
case 'visible' :
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'slug' ,
2022-08-26 14:53:36 -04:00
'terms' => [ 'exclude-from-catalog' , 'exclude-from-search' ],
2019-05-09 13:14:45 -04:00
'operator' => 'NOT IN' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
break ;
case 'hidden' :
2022-08-26 14:53:36 -04:00
$tax_query [] = [
2019-05-09 13:14:45 -04:00
'taxonomy' => 'product_visibility' ,
'field' => 'slug' ,
2022-08-26 14:53:36 -04:00
'terms' => [ 'exclude-from-catalog' , 'exclude-from-search' ],
2019-05-09 13:14:45 -04:00
'operator' => 'AND' ,
2022-08-26 14:53:36 -04:00
];
2019-05-09 13:14:45 -04:00
break ;
2022-02-04 15:00:44 -05:00
} //end switch
} //end if
2019-05-09 13:14:45 -04:00
2023-09-23 03:50:17 -04:00
if ( ! empty ( $where_args [ 'rating' ] ) ) {
$rating = $where_args [ 'rating' ];
$rating_terms = [];
foreach ( $rating as $value ) {
$rating_terms [] = 'rated-' . $value ;
}
$tax_query [] = [
'taxonomy' => 'product_visibility' ,
'field' => 'name' ,
'terms' => $rating_terms ,
];
}
2024-05-16 21:27:54 -04:00
2019-08-22 15:31:34 -04:00
// Process "taxonomyFilter".
2023-08-22 17:14:39 -04:00
$tax_filter_query = [];
2019-08-22 15:31:34 -04:00
if ( ! empty ( $where_args [ 'taxonomyFilter' ] ) ) {
2023-08-22 17:14:39 -04:00
$taxonomy_query = $where_args [ 'taxonomyFilter' ];
2019-08-27 15:10:03 -04:00
2025-10-11 16:29:36 -04:00
// Handle new "or" and "and" syntax.
if ( ! empty ( $taxonomy_query [ 'or' ] ) ) {
$tax_filter_query = $this -> process_taxonomy_filters ( $taxonomy_query [ 'or' ], 'OR' );
} elseif ( ! empty ( $taxonomy_query [ 'and' ] ) ) {
$tax_filter_query = $this -> process_taxonomy_filters ( $taxonomy_query [ 'and' ], 'AND' );
} elseif ( ! empty ( $taxonomy_query [ 'filters' ] ) ) {
// Handle legacy "relation" + "filters" syntax.
$relation = ! empty ( $taxonomy_query [ 'relation' ] ) ? $taxonomy_query [ 'relation' ] : 'AND' ;
$tax_filter_query = $this -> process_taxonomy_filters ( $taxonomy_query [ 'filters' ], $relation );
}
2022-02-04 15:00:44 -05:00
} //end if
2019-08-22 15:31:34 -04:00
2023-08-22 17:14:39 -04:00
if ( ! empty ( $tax_filter_query ) ) {
$tax_query [] = $tax_filter_query ;
}
if ( 1 < count ( $tax_query ) ) {
2019-05-09 13:14:45 -04:00
$tax_query [ 'relation' ] = 'AND' ;
2019-04-23 16:24:45 -04:00
}
2019-04-08 23:52:45 -04:00
if ( ! empty ( $tax_query ) ) {
2022-08-26 14:53:36 -04:00
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2023-08-22 17:14:39 -04:00
$query_args [ 'tax_query' ] = $tax_query ;
2019-04-08 23:52:45 -04:00
}
2022-08-26 14:53:36 -04:00
$meta_query = [];
2019-04-08 23:52:45 -04:00
if ( ! empty ( $where_args [ 'sku' ] ) ) {
2022-08-26 14:53:36 -04:00
$meta_query [] = [
2019-05-09 13:14:45 -04:00
'key' => '_sku' ,
'value' => $where_args [ 'sku' ],
'compare' => 'LIKE' ,
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
}
2024-05-16 21:27:54 -04:00
if ( ! empty ( $where_args [ 'minPrice' ] ) ) {
2024-10-03 00:54:02 +01:00
$query_args [ 'min_price' ] = number_format ( $where_args [ 'minPrice' ], 2 , '' , '' );
2024-05-07 16:50:42 -04:00
}
2019-04-08 23:52:45 -04:00
2024-05-07 16:50:42 -04:00
if ( ! empty ( $where_args [ 'maxPrice' ] ) ) {
2024-10-03 00:54:02 +01:00
$query_args [ 'max_price' ] = number_format ( $where_args [ 'maxPrice' ], 2 , '' , '' );
2019-04-08 23:52:45 -04:00
}
2019-05-09 13:14:45 -04:00
if ( isset ( $where_args [ 'stockStatus' ] ) ) {
2022-08-26 14:53:36 -04:00
$meta_query [] = [
2019-05-09 13:14:45 -04:00
'key' => '_stock_status' ,
'value' => $where_args [ 'stockStatus' ],
'compare' => is_array ( $where_args [ 'stockStatus' ] ) ? 'IN' : '=' ,
2022-08-26 14:53:36 -04:00
];
2019-04-08 23:52:45 -04:00
}
2019-05-09 13:14:45 -04:00
if ( ! empty ( $meta_query ) ) {
2022-08-26 14:53:36 -04:00
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
2023-08-22 17:14:39 -04:00
$query_args [ 'meta_query' ] = $meta_query ;
2019-04-23 22:26:45 -04:00
}
2023-06-13 23:17:02 +03:00
if ( isset ( $where_args [ 'onSale' ] ) && is_bool ( $where_args [ 'onSale' ] ) ) {
2023-08-22 17:14:39 -04:00
$on_sale_key = $where_args [ 'onSale' ] ? 'post__in' : 'post__not_in' ;
2019-04-08 23:52:45 -04:00
$on_sale_ids = \wc_get_product_ids_on_sale ();
2023-08-22 17:14:39 -04:00
$on_sale_ids = empty ( $on_sale_ids ) ? [ 0 ] : $on_sale_ids ;
$query_args [ $on_sale_key ] = $on_sale_ids ;
2019-04-08 23:52:45 -04:00
}
2023-09-11 17:42:26 -04:00
/**
* {@inheritDoc}
*/
$query_args = apply_filters (
'graphql_map_input_fields_to_wp_query' ,
$query_args ,
$where_args ,
$this -> source ,
$this -> args ,
$this -> context ,
$this -> info ,
$this -> post_type
);
2019-04-10 12:26:37 -04:00
/**
* Filter the input fields
* This allows plugins/themes to hook in and alter what $args should be allowed to be passed
* from a GraphQL Query to the WP_Query
*
* @param array $args The mapped query arguments
* @param array $where_args Query "where" args
* @param mixed $source The query results for a query calling this
* @param array $all_args All of the arguments for the query (not just the "where" args)
2023-07-19 23:01:50 +03:00
* @param \WPGraphQL\AppContext $context The AppContext object
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object
2019-04-10 12:26:37 -04:00
* @param mixed|string|array $post_type The post type for the query
*/
2023-08-22 17:14:39 -04:00
$query_args = apply_filters_deprecated (
2019-04-10 12:26:37 -04:00
'graphql_map_input_fields_to_product_query' ,
2023-08-22 17:14:39 -04:00
[
$query_args ,
$where_args ,
$this -> source ,
$this -> args ,
$this -> context ,
$this -> info ,
$this -> post_type ,
],
'0.9.0' ,
'graphql_map_input_fields_to_wp_query'
2019-04-10 12:26:37 -04:00
);
2023-08-22 17:14:39 -04:00
return $query_args ;
2019-04-08 23:52:45 -04:00
}
2020-01-13 14:24:11 -05:00
/**
* Wrapper for "WC_Connection_Functions::is_valid_post_offset()"
*
* @param integer $offset Post ID.
*
* @return bool
*/
public function is_valid_offset ( $offset ) {
2023-08-22 17:14:39 -04:00
return ( bool ) wc_get_product ( $offset );
}
/**
* Adds meta query to the query args.
2024-05-16 21:27:54 -04:00
*
2023-08-22 17:14:39 -04:00
* @param array $value Meta query.
2024-05-16 21:27:54 -04:00
*
2023-08-22 17:14:39 -04:00
* @return \WPGraphQL\WooCommerce\Data\Connection\Product_Connection_Resolver
*/
public function add_meta_query ( $value ) {
if ( ! empty ( $this -> query_args [ 'meta_query' ] ) ) {
$this -> query_args [ 'meta_query' ] = $value ; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
} else {
$this -> query_args [ 'meta_query' ][] = $value ;
$this -> query_args [ 'meta_query' ][ 'relation' ] = 'AND' ;
}
return $this ;
}
/**
* Adds tax query to the query args.
*
* @param array $value Tax query.
*
* @return \WPGraphQL\WooCommerce\Data\Connection\Product_Connection_Resolver
*/
public function add_tax_query ( $value ) {
2023-12-22 18:06:12 -05:00
if ( empty ( $this -> query_args [ 'tax_query' ] ) ) {
2023-08-22 17:14:39 -04:00
$this -> query_args [ 'tax_query' ] = $value ; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
} else {
$this -> query_args [ 'tax_query' ][] = $value ;
$this -> query_args [ 'tax_query' ][ 'relation' ] = 'AND' ;
}
return $this ;
2020-01-13 14:24:11 -05:00
}
2023-09-15 23:31:24 -04:00
/**
* {@inheritDoc}
*/
public function get_page_info () {
$page_info = parent :: get_page_info ();
$page_info [ 'found' ] = $this -> query -> found_posts ;
return $page_info ;
}
2019-03-31 00:21:31 -04:00
}