mirror of
https://github.com/wp-graphql/wp-graphql-woocommerce.git
synced 2026-08-14 12:53:44 +02:00
feat: collectionStats query added (#785)
* feat: collectionStats query added * fix: `collectionStats` query completed and tested. * chore: Linter and PHPStan compliance met * chore: ProductTaxonomy values fixed. * chore: CollectionStatsQueryTest tweaked for CI
This commit is contained in:
@@ -43,6 +43,8 @@ class Type_Registry {
|
||||
Type\WPEnum\Orders_Orderby_Enum::register();
|
||||
Type\WPEnum\Id_Type_Enums::register();
|
||||
Type\WPEnum\Cart_Error_Type::register();
|
||||
Type\WPEnum\Product_Attribute_Enum::register();
|
||||
Type\WPEnum\Attribute_Operator_Enum::register();
|
||||
|
||||
/**
|
||||
* InputObjects.
|
||||
@@ -60,6 +62,9 @@ class Type_Registry {
|
||||
Type\WPInputObject\Product_Taxonomy_Filter_Input::register();
|
||||
Type\WPInputObject\Product_Taxonomy_Input::register();
|
||||
Type\WPInputObject\Orderby_Inputs::register();
|
||||
Type\WPInputObject\Collection_Stats_Query_Input::register();
|
||||
Type\WPInputObject\Collection_Stats_Where_Args::register();
|
||||
Type\WPInputObject\Product_Attribute_Filter_Input::register();
|
||||
|
||||
/**
|
||||
* Interfaces.
|
||||
@@ -104,6 +109,7 @@ class Type_Registry {
|
||||
Type\WPObject\Cart_Error_Types::register();
|
||||
Type\WPObject\Payment_Token_Types::register();
|
||||
Type\WPObject\Country_State_Type::register();
|
||||
Type\WPObject\Collection_Stats_Type::register();
|
||||
|
||||
/**
|
||||
* Object fields.
|
||||
|
||||
@@ -236,6 +236,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
|
||||
require $include_directory_path . 'type/enum/class-tax-rate-connection-orderby-enum.php';
|
||||
require $include_directory_path . 'type/enum/class-tax-status.php';
|
||||
require $include_directory_path . 'type/enum/class-taxonomy-operator.php';
|
||||
require $include_directory_path . 'type/enum/class-attribute-operator-enum.php';
|
||||
require $include_directory_path . 'type/enum/class-product-attribute-enum.php';
|
||||
|
||||
// Include interface type class files.
|
||||
require $include_directory_path . 'type/interface/class-attribute.php';
|
||||
@@ -278,6 +280,7 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
|
||||
require $include_directory_path . 'type/object/class-variation-attribute-type.php';
|
||||
require $include_directory_path . 'type/object/class-payment-token-types.php';
|
||||
require $include_directory_path . 'type/object/class-country-state-type.php';
|
||||
require $include_directory_path . 'type/object/class-collection-stats-type.php';
|
||||
|
||||
// Include input type class files.
|
||||
require $include_directory_path . 'type/input/class-cart-item-input.php';
|
||||
@@ -293,6 +296,9 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
|
||||
require $include_directory_path . 'type/input/class-product-taxonomy-input.php';
|
||||
require $include_directory_path . 'type/input/class-shipping-line-input.php';
|
||||
require $include_directory_path . 'type/input/class-tax-rate-connection-orderby-input.php';
|
||||
require $include_directory_path . 'type/input/class-collection-stats-query-input.php';
|
||||
require $include_directory_path . 'type/input/class-collection-stats-where-args.php';
|
||||
require $include_directory_path . 'type/input/class-product-attribute-filter-input.php';
|
||||
|
||||
// Include mutation type class files.
|
||||
require $include_directory_path . 'mutation/class-cart-add-fee.php';
|
||||
|
||||
@@ -427,6 +427,10 @@ class Products {
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'Include variations in the result set.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'rating' => [
|
||||
'type' => [ 'list_of' => 'Integer' ],
|
||||
'description' => __( 'Limit result set to products with a specific average rating. Must be between 1 and 5', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( wc_tax_enabled() ) {
|
||||
|
||||
@@ -466,6 +466,19 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
|
||||
}//end switch
|
||||
}//end if
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
// Process "taxonomyFilter".
|
||||
$tax_filter_query = [];
|
||||
if ( ! empty( $where_args['taxonomyFilter'] ) ) {
|
||||
@@ -563,6 +576,8 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
|
||||
$query_args[ $on_sale_key ] = $on_sale_ids;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* WPEnum Type - AttributeOperatorEnum
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPEnum
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPEnum;
|
||||
|
||||
/**
|
||||
* Class Attribute_Operator_Enum
|
||||
*/
|
||||
class Attribute_Operator_Enum {
|
||||
/**
|
||||
* Registers type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_enum_type(
|
||||
'AttributeOperatorEnum',
|
||||
[
|
||||
'description' => __( 'Collection statistic attributes operators', 'wp-graphql-woocommerce' ),
|
||||
'values' => [
|
||||
'IN' => [ 'value' => 'IN' ],
|
||||
'NOT_IN' => [ 'value' => 'NOT IN' ],
|
||||
'AND' => [ 'value' => 'AND' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* WPEnum Type - ProductAttributeEnum
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPEnum
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPEnum;
|
||||
|
||||
use WPGraphQL\Type\WPEnumType;
|
||||
|
||||
/**
|
||||
* Class Product_Attribute_Enum
|
||||
*/
|
||||
class Product_Attribute_Enum {
|
||||
/**
|
||||
* Registers type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
// Get values from product attributes.
|
||||
$taxonomy_values = [];
|
||||
$taxonomies = wc_get_attribute_taxonomy_names();
|
||||
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$tax_object = get_taxonomy( $taxonomy );
|
||||
|
||||
if ( false !== $tax_object && in_array( 'product', $tax_object->object_type, true ) ) {
|
||||
$taxonomy_values[ WPEnumType::get_safe_name( $taxonomy ) ] = [ 'value' => $taxonomy ];
|
||||
}
|
||||
}
|
||||
|
||||
register_graphql_enum_type(
|
||||
'ProductAttributeEnum',
|
||||
[
|
||||
'description' => __( 'Product attribute taxonomies', 'wp-graphql-woocommerce' ),
|
||||
'values' => $taxonomy_values,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class Product_Taxonomy {
|
||||
$tax_object = get_taxonomy( $taxonomy );
|
||||
|
||||
if ( false !== $tax_object && in_array( 'product', $tax_object->object_type, true ) ) {
|
||||
$taxonomy_values[ WPEnumType::get_safe_name( $tax_object->graphql_single_name ) ] = [ 'value' => $taxonomy ];
|
||||
$taxonomy_values[ WPEnumType::get_safe_name( $taxonomy ) ] = [ 'value' => $taxonomy ];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* WPInputObjectType - CollectionStatsQueryInput
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPInputObject
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
|
||||
|
||||
/**
|
||||
* Class Collection_Stats_Query_Input
|
||||
*/
|
||||
class Collection_Stats_Query_Input {
|
||||
/**
|
||||
* Registers type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_input_type(
|
||||
'CollectionStatsQueryInput',
|
||||
[
|
||||
'description' => __( 'Taxonomy query', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'taxonomy' => [
|
||||
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
|
||||
'description' => __( 'Product Taxonomy', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'relation' => [
|
||||
'type' => [ 'non_null' => 'RelationEnum' ],
|
||||
'description' => __( 'Taxonomy relation to query', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* WPInputObjectType - CollectionStatsWhereArgs
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPInputObject
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
|
||||
|
||||
/**
|
||||
* Class Collection_Stats_Where_Args
|
||||
*/
|
||||
class Collection_Stats_Where_Args {
|
||||
/**
|
||||
* Registers type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_input_type(
|
||||
'CollectionStatsWhereArgs',
|
||||
[
|
||||
'description' => __( 'Arguments used to filter the collection results', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'search' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'Limit result set to products based on a keyword search.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'slugIn' => [
|
||||
'type' => [ 'list_of' => 'String' ],
|
||||
'description' => __( 'Limit result set to products with specific slugs.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'typeIn' => [
|
||||
'type' => [ 'list_of' => 'ProductTypesEnum' ],
|
||||
'description' => __( 'Limit result set to products assigned to a group of specific types.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'exclude' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Ensure result set excludes specific IDs.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'include' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Limit result set to specific ids.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'sku' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'featured' => [
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'Limit result set to featured products.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'parentIn' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Specify objects whose parent is in an array.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'parentNotIn' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Specify objects whose parent is not in an array.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'categoryIn' => [
|
||||
'type' => [ 'list_of' => 'String' ],
|
||||
'description' => __( 'Limit result set to products assigned to a group of specific categories by name.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'categoryIdIn' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Limit result set to products assigned to a specific group of category IDs.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'tagIn' => [
|
||||
'type' => [ 'list_of' => 'String' ],
|
||||
'description' => __( 'Limit result set to products assigned to a specific group of tags by name.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'tagIdIn' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'Limit result set to products assigned to a specific group of tag IDs.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'attributes' => [
|
||||
'type' => [ 'list_of' => 'ProductAttributeFilterInput' ],
|
||||
'description' => __( 'Limit result set to products with a specific attribute. Use the taxonomy name/attribute slug.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'stockStatus' => [
|
||||
'type' => [ 'list_of' => 'StockStatusEnum' ],
|
||||
'description' => __( 'Limit result set to products in stock or out of stock.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'onSale' => [
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'Limit result set to products on sale.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'minPrice' => [
|
||||
'type' => 'Float',
|
||||
'description' => __( 'Limit result set to products based on a minimum price.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'maxPrice' => [
|
||||
'type' => 'Float',
|
||||
'description' => __( 'Limit result set to products based on a maximum price.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'visibility' => [
|
||||
'type' => 'CatalogVisibilityEnum',
|
||||
'description' => __( 'Limit result set to products with a specific visibility level.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'rating' => [
|
||||
'type' => [ 'list_of' => 'Integer' ],
|
||||
'description' => __( 'Limit result set to products with a specific average rating. Must be between 1 and 5', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* WPInputObjectType - ProductAttributeFilterInput
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPInputObject
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
|
||||
|
||||
/**
|
||||
* Class Product_Attribute_Filter_Input
|
||||
*/
|
||||
class Product_Attribute_Filter_Input {
|
||||
/**
|
||||
* Registers type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_input_type(
|
||||
'ProductAttributeFilterInput',
|
||||
[
|
||||
'description' => __( 'Product filter', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'taxonomy' => [
|
||||
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
|
||||
'description' => __( 'Which field to select taxonomy term by.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'terms' => [
|
||||
'type' => [ 'list_of' => 'String' ],
|
||||
'description' => __( 'A list of term slugs', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'ids' => [
|
||||
'type' => [ 'list_of' => 'Int' ],
|
||||
'description' => __( 'A list of term ids', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'operator' => [
|
||||
'type' => 'AttributeOperatorEnum',
|
||||
'description' => __( 'Filter operation type', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/**
|
||||
* WPObject Type - Collection_Stats_Type
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Type\WPObject
|
||||
* @since TBD
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPObject;
|
||||
|
||||
/**
|
||||
* Class Collection_Stats_Type
|
||||
*/
|
||||
class Collection_Stats_Type {
|
||||
/**
|
||||
* Register CollectionStats type to the WPGraphQL schema
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_object_type(
|
||||
'PriceRange',
|
||||
[
|
||||
'eagerlyLoadType' => true,
|
||||
'description' => __( 'Price range', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'minPrice' => [
|
||||
'type' => 'String',
|
||||
'args' => [
|
||||
'format' => [
|
||||
'type' => 'PricingFieldFormatEnum',
|
||||
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
'description' => __( 'Minimum price', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source, array $args ) {
|
||||
if ( empty( $source['min_price'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
|
||||
return $source['min_price'];
|
||||
}
|
||||
|
||||
return wc_graphql_price( $source['min_price'] );
|
||||
},
|
||||
],
|
||||
'maxPrice' => [
|
||||
'type' => 'String',
|
||||
'args' => [
|
||||
'format' => [
|
||||
'type' => 'PricingFieldFormatEnum',
|
||||
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
'description' => __( 'Maximum price', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source, array $args ) {
|
||||
if ( empty( $source['max_price'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
|
||||
return $source['max_price'];
|
||||
}
|
||||
|
||||
return wc_graphql_price( $source['max_price'] );
|
||||
},
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_graphql_object_type(
|
||||
'AttributeCount',
|
||||
[
|
||||
'eagerlyLoadType' => true,
|
||||
'description' => __( 'Product attribute terms count', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'slug' => [
|
||||
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
|
||||
'description' => __( 'Attribute name', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source ) {
|
||||
return $source->name;
|
||||
},
|
||||
],
|
||||
'label' => [
|
||||
'type' => [ 'non_null' => 'String' ],
|
||||
'description' => __( 'Attribute taxonomy', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source ) {
|
||||
$taxonomy = get_taxonomy( $source->name );
|
||||
|
||||
if ( ! $taxonomy instanceof \WP_Taxonomy ) {
|
||||
return null;
|
||||
}
|
||||
return $taxonomy->label;
|
||||
},
|
||||
],
|
||||
'name' => [
|
||||
'type' => [ 'non_null' => 'String' ],
|
||||
'description' => __( 'Attribute name', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source ) {
|
||||
$taxonomy = get_taxonomy( $source->name );
|
||||
|
||||
if ( ! $taxonomy instanceof \WP_Taxonomy ) {
|
||||
return null;
|
||||
}
|
||||
return $taxonomy->labels->singular_name;
|
||||
},
|
||||
],
|
||||
'terms' => [
|
||||
'type' => [ 'list_of' => 'SingleAttributeCount' ],
|
||||
'description' => __( 'Attribute terms', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_graphql_object_type(
|
||||
'SingleAttributeCount',
|
||||
[
|
||||
'eagerlyLoadType' => true,
|
||||
'description' => __( 'Single attribute term count', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'termId' => [
|
||||
'type' => [ 'non_null' => 'ID' ],
|
||||
'description' => __( 'Term ID', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'count' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of products.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'node' => [
|
||||
'type' => 'TermNode',
|
||||
'description' => __( 'Term object.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source ) {
|
||||
if ( empty( $source->termId ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Term object.
|
||||
*
|
||||
* @var \WP_Term $term
|
||||
*/
|
||||
$term = get_term( $source->termId ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
if ( ! $term instanceof \WP_Term ) {
|
||||
return null;
|
||||
}
|
||||
return new \WPGraphQL\Model\Term( $term );
|
||||
},
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_graphql_object_type(
|
||||
'RatingCount',
|
||||
[
|
||||
'eagerlyLoadType' => true,
|
||||
'description' => __( 'Single rating count', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'rating' => [
|
||||
'type' => [ 'non_null' => 'Int' ],
|
||||
'description' => __( 'Average rating', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'count' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of products', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_graphql_object_type(
|
||||
'StockStatusCount',
|
||||
[
|
||||
'eagerlyLoadType' => true,
|
||||
'description' => __( 'Single stock status count', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'status' => [
|
||||
'type' => [ 'non_null' => 'StockStatusEnum' ],
|
||||
'description' => __( 'Status', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'count' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of products.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_graphql_object_type(
|
||||
'CollectionStats',
|
||||
[
|
||||
'description' => __( 'Data about a collection of products', 'wp-graphql-woocommerce' ),
|
||||
'fields' => [
|
||||
'priceRange' => [
|
||||
'type' => 'PriceRange',
|
||||
'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source ) {
|
||||
$min_price = ! empty( $source['min_price'] ) ? $source['min_price'] : null;
|
||||
$max_price = ! empty( $source['max_price'] ) ? $source['max_price'] : null;
|
||||
return compact( 'min_price', 'max_price' );
|
||||
},
|
||||
],
|
||||
'attributeCounts' => [
|
||||
'type' => [ 'list_of' => 'AttributeCount' ],
|
||||
'args' => [
|
||||
'page' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'perPage' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
'description' => __( 'Returns number of products within attribute terms', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source, $args ) {
|
||||
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
|
||||
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
|
||||
$attribute_counts = ! empty( $source['attribute_counts'] ) ? $source['attribute_counts'] : [];
|
||||
$attribute_counts = array_slice(
|
||||
$attribute_counts,
|
||||
( $page - 1 ) * $per_page,
|
||||
0 < $per_page ? $per_page : null
|
||||
);
|
||||
|
||||
return array_map(
|
||||
static function ( $name, $terms ) {
|
||||
return (object) compact( 'name', 'terms' );
|
||||
},
|
||||
array_keys( $attribute_counts ),
|
||||
array_values( $attribute_counts )
|
||||
);
|
||||
},
|
||||
],
|
||||
'ratingCounts' => [
|
||||
'type' => [ 'list_of' => 'RatingCount' ],
|
||||
'args' => [
|
||||
'page' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'perPage' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
'description' => __( 'Returns number of products with each average rating', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source, $args ) {
|
||||
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
|
||||
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
|
||||
$rating_counts = ! empty( $source['rating_counts'] ) ? $source['rating_counts'] : [];
|
||||
$rating_counts = array_slice(
|
||||
$rating_counts,
|
||||
( $page - 1 ) * $per_page,
|
||||
0 < $per_page ? $per_page : null
|
||||
);
|
||||
|
||||
return $rating_counts;
|
||||
},
|
||||
],
|
||||
'stockStatusCounts' => [
|
||||
'type' => [ 'list_of' => 'StockStatusCount' ],
|
||||
'args' => [
|
||||
'page' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'perPage' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
],
|
||||
'description' => __( 'Returns number of products with each stock status', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $source, $args ) {
|
||||
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
|
||||
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
|
||||
$stock_status_counts = ! empty( $source['stock_status_counts'] ) ? $source['stock_status_counts'] : [];
|
||||
$stock_status_counts = array_slice(
|
||||
$stock_status_counts,
|
||||
( $page - 1 ) * $per_page,
|
||||
0 < $per_page ? $per_page : null
|
||||
);
|
||||
|
||||
return $stock_status_counts;
|
||||
},
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the WP_Rest_Request instance used for the resolution of a
|
||||
* statistics for a product connection.
|
||||
*
|
||||
* @param array $where_args Arguments used to filter the connection results.
|
||||
*
|
||||
* @return \WP_REST_Request
|
||||
*/
|
||||
public static function prepare_rest_request( array $where_args = [] ) /* @phpstan-ignore-line */ {
|
||||
$request = new \WP_REST_Request();
|
||||
if ( empty( $where_args ) ) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$key_mapping = [
|
||||
'slugIn' => 'slug',
|
||||
'typeIn' => 'type',
|
||||
'categoryIdIn' => 'category',
|
||||
'tagIn' => 'tag',
|
||||
'onSale' => 'on_sale',
|
||||
'stockStatus' => 'stock_status',
|
||||
'visibility' => 'catalog_visibility',
|
||||
'minPrice' => 'min_price',
|
||||
'maxPrice' => 'max_price',
|
||||
];
|
||||
|
||||
$needs_formatting = [ 'attributes', 'categoryIn' ];
|
||||
foreach ( $where_args as $key => $value ) {
|
||||
if ( in_array( $key, $needs_formatting, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$request->set_param( $key_mapping[ $key ] ?? $key, $value );
|
||||
}
|
||||
|
||||
if ( ! empty( $where_args['categoryIn'] ) ) {
|
||||
$category_ids = array_map(
|
||||
static function ( $category ) {
|
||||
$term = get_term_by( 'slug', $category, 'product_cat' );
|
||||
if ( is_object( $term ) ) {
|
||||
return $term->term_id;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
$where_args['categoryIn']
|
||||
);
|
||||
$set_category = $request->get_param( 'category' );
|
||||
if ( ! empty( $set_category ) ) {
|
||||
$category_ids[] = $set_category;
|
||||
$request->set_param( 'category', $category_ids );
|
||||
} else {
|
||||
$request->set_param( 'category', $category_ids );
|
||||
}
|
||||
$request->set_param( 'category_operator', 'and' );
|
||||
}
|
||||
|
||||
if ( ! empty( $where_args['attributes'] ) ) {
|
||||
$attributes = [];
|
||||
foreach ( $where_args['attributes'] as $filter ) {
|
||||
if ( str_starts_with( $filter['taxonomy'], 'pa_' ) ) {
|
||||
$attribute = [];
|
||||
$attribute['attribute'] = $filter['taxonomy'];
|
||||
if ( ! empty( $filter['terms'] ) ) {
|
||||
$attribute['slug'] = $filter['terms'];
|
||||
} elseif ( ! empty( $filter['ids'] ) ) {
|
||||
$attribute['term_id'] = $filter['ids'];
|
||||
}
|
||||
$attribute['operator'] = ! empty( $filter['operator'] ) ? strtolower( $filter['operator'] ) : 'in';
|
||||
$attributes[] = $attribute;
|
||||
} else {
|
||||
if ( ! empty( $filter['ids'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$taxonomy = $filter['taxonomy'];
|
||||
$request->set_param( "_unstable_tax_{$taxonomy}", $filter['ids'] );
|
||||
$request->set_param( "_unstable_tax_{$taxonomy}_operator", strtolower( $filter['operator'] ) );
|
||||
}
|
||||
}
|
||||
if ( ! empty( $attributes ) ) {
|
||||
$request->set_param( 'attributes', $attributes );
|
||||
}
|
||||
}//end if
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Type\WPObject;
|
||||
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\ProductQueryFilters;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
use GraphQL\Error\UserError;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
@@ -515,6 +516,120 @@ class Root_Query {
|
||||
return [];
|
||||
},
|
||||
],
|
||||
'collectionStats' => [
|
||||
'type' => 'CollectionStats',
|
||||
'args' => [
|
||||
'calculatePriceRange' => [
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'calculateRatingCounts' => [
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'If true, calculates rating counts for products in the collection.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'calculateStockStatusCounts' => [
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'If true, calculates stock counts for products in the collection.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
'taxonomies' => [
|
||||
'type' => [ 'list_of' => 'CollectionStatsQueryInput' ],
|
||||
],
|
||||
'where' => [
|
||||
'type' => 'CollectionStatsWhereArgs',
|
||||
],
|
||||
],
|
||||
'description' => __( 'Statistics for a product taxonomy query', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => static function ( $_, $args ) {
|
||||
$filters = new ProductQueryFilters(); // @phpstan-ignore-line
|
||||
$data = [
|
||||
'min_price' => null,
|
||||
'max_price' => null,
|
||||
'attribute_counts' => null,
|
||||
'stock_status_counts' => null,
|
||||
'rating_counts' => null,
|
||||
];
|
||||
|
||||
// Process client-side filters.
|
||||
$request = Collection_Stats_Type::prepare_rest_request( $args['where'] ?? [] );
|
||||
|
||||
// Format taxonomies.
|
||||
if ( ! empty( $args['taxonomies'] ) ) {
|
||||
$calculate_attribute_counts = [];
|
||||
foreach ( $args['taxonomies'] as $attribute_to_count ) {
|
||||
$calculate_attribute_counts[] = [
|
||||
'taxonomy' => $attribute_to_count['taxonomy'],
|
||||
'query_type' => strtolower( $attribute_to_count['relation'] ),
|
||||
];
|
||||
}
|
||||
$request->set_param( 'calculate_attribute_counts', $calculate_attribute_counts );
|
||||
}
|
||||
|
||||
$request->set_param( 'calculate_price_range', $args['calculatePriceRange'] ?? false );
|
||||
$request->set_param( 'calculate_stock_status_counts', $args['calculateStockStatusCounts'] ?? false );
|
||||
$request->set_param( 'calculate_rating_counts', $args['calculateRatingCounts'] ?? false );
|
||||
|
||||
|
||||
|
||||
if ( ! empty( $request['calculate_price_range'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$filter_request->set_param( 'min_price', null );
|
||||
$filter_request->set_param( 'max_price', null );
|
||||
|
||||
$price_results = $filters->get_filtered_price( $filter_request ); // @phpstan-ignore-line
|
||||
$data['min_price'] = $price_results->min_price;
|
||||
$data['max_price'] = $price_results->max_price;
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_stock_status_counts'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$counts = $filters->get_stock_status_counts( $filter_request ); // @phpstan-ignore-line
|
||||
|
||||
$data['stock_status_counts'] = [];
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['stock_status_counts'][] = (object) [
|
||||
'status' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_attribute_counts'] ) ) {
|
||||
foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) {
|
||||
if ( ! isset( $attributes_to_count['taxonomy'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$taxonomy = $attributes_to_count['taxonomy'];
|
||||
$counts = $filters->get_attribute_counts( $request, $taxonomy ); // @phpstan-ignore-line
|
||||
|
||||
$data['attribute_counts'][ $taxonomy ] = [];
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['attribute_counts'][ $taxonomy ][] = (object) [
|
||||
'taxonomy' => $taxonomy,
|
||||
'termId' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_rating_counts'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$counts = $filters->get_rating_counts( $filter_request ); // @phpstan-ignore-line
|
||||
$data['rating_counts'] = [];
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['rating_counts'][] = (object) [
|
||||
'rating' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
class CollectionStatsQueryTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
update_option( 'woocommerce_attribute_lookup_enabled', 'yes' );
|
||||
update_option( 'woocommerce_attribute_lookup_direct_updates', 'yes' );
|
||||
}
|
||||
|
||||
public function testCollectionStatsQuery() {
|
||||
$this->factory->product_variation->createSome(
|
||||
$this->factory->product->createVariable()
|
||||
);
|
||||
$this->factory->product->createSimple();
|
||||
$this->factory->product->createSimple();
|
||||
$this->factory->product_variation->createSome(
|
||||
$this->factory->product->createVariable()
|
||||
);
|
||||
|
||||
$query = '
|
||||
query ($where: CollectionStatsWhereArgs, $taxonomies: [CollectionStatsQueryInput]) {
|
||||
collectionStats(
|
||||
calculatePriceRange: true
|
||||
calculateRatingCounts: true
|
||||
calculateStockStatusCounts: true
|
||||
taxonomies: $taxonomies
|
||||
where: $where
|
||||
) {
|
||||
attributeCounts {
|
||||
name
|
||||
slug
|
||||
label
|
||||
terms {
|
||||
node { slug }
|
||||
termId
|
||||
count
|
||||
}
|
||||
}
|
||||
stockStatusCounts {
|
||||
status
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$variables = [
|
||||
'where' => [
|
||||
'attributes' => [
|
||||
[
|
||||
'taxonomy' => 'PA_COLOR',
|
||||
'terms' => 'red',
|
||||
'operator' => 'IN',
|
||||
],
|
||||
]
|
||||
],
|
||||
'taxonomies' => [
|
||||
[
|
||||
'taxonomy' => 'PA_COLOR',
|
||||
'relation' => 'AND',
|
||||
]
|
||||
]
|
||||
];
|
||||
$response = $this->graphql( compact( 'query', 'variables' ) );
|
||||
$expected = [
|
||||
$this->expectedNode(
|
||||
'collectionStats.attributeCounts',
|
||||
[
|
||||
$this->expectedField('slug', 'PA_COLOR' ),
|
||||
$this->expectedField('label', 'color' ),
|
||||
$this->expectedField('name', 'color' ),
|
||||
$this->expectedNode(
|
||||
'terms',
|
||||
[
|
||||
$this->expectedField( 'node.slug', 'red' ),
|
||||
$this->expectedField( 'count', 2 ),
|
||||
$this->expectedField( 'termId', self::NOT_FALSY ),
|
||||
]
|
||||
),
|
||||
$this->expectedNode(
|
||||
'terms',
|
||||
[
|
||||
$this->expectedField( 'node.slug', 'blue' ),
|
||||
$this->expectedField( 'count', 2 ),
|
||||
$this->expectedField( 'termId', self::NOT_FALSY ),
|
||||
]
|
||||
),
|
||||
$this->expectedNode(
|
||||
'terms',
|
||||
[
|
||||
$this->expectedField( 'node.slug', 'green' ),
|
||||
$this->expectedField( 'count', 2 ),
|
||||
$this->expectedField( 'termId', self::NOT_FALSY ),
|
||||
]
|
||||
),
|
||||
],
|
||||
0
|
||||
),
|
||||
$this->expectedNode(
|
||||
'collectionStats.stockStatusCounts',
|
||||
[
|
||||
$this->expectedField( 'status', 'IN_STOCK' ),
|
||||
$this->expectedField( 'count', 2 ),
|
||||
]
|
||||
),
|
||||
$this->expectedNode(
|
||||
'collectionStats.stockStatusCounts',
|
||||
[
|
||||
$this->expectedField( 'status', 'OUT_OF_STOCK' ),
|
||||
$this->expectedField( 'count', 0 ),
|
||||
]
|
||||
),
|
||||
$this->expectedNode(
|
||||
'collectionStats.stockStatusCounts',
|
||||
[
|
||||
$this->expectedField( 'status', 'ON_BACKORDER' ),
|
||||
$this->expectedField( 'count', 0 ),
|
||||
]
|
||||
),
|
||||
];
|
||||
$this->assertQuerySuccessful( $response, $expected );
|
||||
}
|
||||
}
|
||||
@@ -792,11 +792,11 @@ class ProductQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
|
||||
'relation' => 'AND',
|
||||
'filters' => [
|
||||
[
|
||||
'taxonomy' => 'PRODUCTCATEGORY',
|
||||
'taxonomy' => 'PRODUCT_CAT',
|
||||
'terms' => [ 'category-three' ],
|
||||
],
|
||||
[
|
||||
'taxonomy' => 'PRODUCTCATEGORY',
|
||||
'taxonomy' => 'PRODUCT_CAT',
|
||||
'terms' => [ 'category-four' ],
|
||||
'operator' => 'NOT_IN',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user