fix: product connection resolution refactored to better work with the ProductQuery class (#880)

* fix: product connection resolution refactored to better work with the ProductQuery class

* chore: Linter and PHPStan compliance met

* devops: New product connection tests implemented and passing

* fix: products connection pricing filters fixed

* devops: CartTransactionQueueCest skipped until failing PHP version removed from CI matrix
This commit is contained in:
Geoff Taylor
2024-08-06 18:07:37 -04:00
committed by GitHub
parent a37c68e7e9
commit d9d968ac91
24 changed files with 1304 additions and 794 deletions
+2 -2
View File
@@ -26,8 +26,8 @@
},
"require-dev": {
"axepress/wp-graphql-cs": "^2.0.0-beta",
"axepress/wp-graphql-stubs": "~1.16.0",
"php-stubs/woocommerce-stubs": "7.9.0",
"axepress/wp-graphql-stubs": "^1.27.1",
"php-stubs/woocommerce-stubs": "9.1.0",
"phpstan/extension-installer": "^1.3",
"phpstan/phpdoc-parser": "^1.22.0",
"phpstan/phpstan": "^1.10",
Generated
+64 -62
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0a63256d1a0b55f5be349b050cf48c59",
"content-hash": "fe319feb8c0b2d7478695d8dce39b020",
"packages": [
{
"name": "firebase/php-jwt",
@@ -185,16 +185,16 @@
},
{
"name": "axepress/wp-graphql-stubs",
"version": "v1.16.0+repack.1",
"version": "v1.27.1",
"source": {
"type": "git",
"url": "https://github.com/AxeWP/wp-graphql-stubs.git",
"reference": "47ce4c7e0c715bea84bc84b675e274b94a8c22f4"
"reference": "84ef51264b142d92c0ac9aa39edf2cce5f0ac3af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AxeWP/wp-graphql-stubs/zipball/47ce4c7e0c715bea84bc84b675e274b94a8c22f4",
"reference": "47ce4c7e0c715bea84bc84b675e274b94a8c22f4",
"url": "https://api.github.com/repos/AxeWP/wp-graphql-stubs/zipball/84ef51264b142d92c0ac9aa39edf2cce5f0ac3af",
"reference": "84ef51264b142d92c0ac9aa39edf2cce5f0ac3af",
"shasum": ""
},
"require": {
@@ -225,7 +225,7 @@
],
"support": {
"issues": "https://github.com/AxeWP/wp-graphql-stubs/issues",
"source": "https://github.com/AxeWP/wp-graphql-stubs/tree/v1.16.0+repack.1"
"source": "https://github.com/AxeWP/wp-graphql-stubs/tree/v1.27.1"
},
"funding": [
{
@@ -233,7 +233,7 @@
"type": "github"
}
],
"time": "2023-10-01T17:13:28+00:00"
"time": "2024-07-04T12:37:29+00:00"
},
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
@@ -315,16 +315,16 @@
},
{
"name": "php-stubs/woocommerce-stubs",
"version": "v7.9.0",
"version": "v9.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-stubs/woocommerce-stubs.git",
"reference": "3a2f522e29451490c357af550227795d2b0fc55a"
"reference": "2c95c633362d1f4f531f69e5db63bb19399d8b58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/3a2f522e29451490c357af550227795d2b0fc55a",
"reference": "3a2f522e29451490c357af550227795d2b0fc55a",
"url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/2c95c633362d1f4f531f69e5db63bb19399d8b58",
"reference": "2c95c633362d1f4f531f69e5db63bb19399d8b58",
"shasum": ""
},
"require": {
@@ -353,33 +353,34 @@
],
"support": {
"issues": "https://github.com/php-stubs/woocommerce-stubs/issues",
"source": "https://github.com/php-stubs/woocommerce-stubs/tree/v7.9.0"
"source": "https://github.com/php-stubs/woocommerce-stubs/tree/v9.1.0"
},
"time": "2023-07-17T22:41:38+00:00"
"time": "2024-07-11T10:55:02+00:00"
},
{
"name": "php-stubs/wordpress-stubs",
"version": "v6.5.3",
"version": "v6.6.0",
"source": {
"type": "git",
"url": "https://github.com/php-stubs/wordpress-stubs.git",
"reference": "e611a83292d02055a25f83291a98fadd0c21e092"
"reference": "86e8753e89d59849276dcdd91b9a7dd78bb4abe2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/e611a83292d02055a25f83291a98fadd0c21e092",
"reference": "e611a83292d02055a25f83291a98fadd0c21e092",
"url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/86e8753e89d59849276dcdd91b9a7dd78bb4abe2",
"reference": "86e8753e89d59849276dcdd91b9a7dd78bb4abe2",
"shasum": ""
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"nikic/php-parser": "^4.13",
"php": "^7.4 || ~8.0.0",
"php": "^7.4 || ^8.0",
"php-stubs/generator": "^0.8.3",
"phpdocumentor/reflection-docblock": "5.3",
"phpdocumentor/reflection-docblock": "^5.4.1",
"phpstan/phpstan": "^1.10.49",
"phpunit/phpunit": "^9.5",
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.11"
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0",
"wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
},
"suggest": {
"paragonie/sodium_compat": "Pure PHP implementation of libsodium",
@@ -400,9 +401,9 @@
],
"support": {
"issues": "https://github.com/php-stubs/wordpress-stubs/issues",
"source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.5.3"
"source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.6.0"
},
"time": "2024-05-08T02:12:31+00:00"
"time": "2024-07-17T08:50:38+00:00"
},
{
"name": "phpcompatibility/php-compatibility",
@@ -776,16 +777,16 @@
},
{
"name": "phpstan/extension-installer",
"version": "1.3.1",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/extension-installer.git",
"reference": "f45734bfb9984c6c56c4486b71230355f066a58a"
"reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a",
"reference": "f45734bfb9984c6c56c4486b71230355f066a58a",
"url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203",
"reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203",
"shasum": ""
},
"require": {
@@ -814,22 +815,22 @@
"description": "Composer plugin for automatic installation of PHPStan extensions",
"support": {
"issues": "https://github.com/phpstan/extension-installer/issues",
"source": "https://github.com/phpstan/extension-installer/tree/1.3.1"
"source": "https://github.com/phpstan/extension-installer/tree/1.4.1"
},
"time": "2023-05-24T08:59:17+00:00"
"time": "2024-06-10T08:20:49+00:00"
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.29.0",
"version": "1.29.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc"
"reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc",
"reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4",
"reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4",
"shasum": ""
},
"require": {
@@ -861,22 +862,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1"
},
"time": "2024-05-06T12:04:23+00:00"
"time": "2024-05-31T08:52:43+00:00"
},
{
"name": "phpstan/phpstan",
"version": "1.11.1",
"version": "1.11.8",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b"
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e524358f930e41a2b4cca1320e3b04fc26b39e0b",
"reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"shasum": ""
},
"require": {
@@ -921,20 +922,20 @@
"type": "github"
}
],
"time": "2024-05-15T08:00:59+00:00"
"time": "2024-07-24T07:01:22+00:00"
},
{
"name": "sirbrillig/phpcs-variable-analysis",
"version": "v2.11.18",
"version": "v2.11.19",
"source": {
"type": "git",
"url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
"reference": "ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0"
"reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0",
"reference": "ca242a0b7309e0f9d1f73b236e04ecf4ca3248d0",
"url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1",
"reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1",
"shasum": ""
},
"require": {
@@ -979,7 +980,7 @@
"source": "https://github.com/sirbrillig/phpcs-variable-analysis",
"wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki"
},
"time": "2024-04-13T16:42:46+00:00"
"time": "2024-06-26T20:08:34+00:00"
},
{
"name": "slevomat/coding-standard",
@@ -1048,16 +1049,16 @@
},
{
"name": "squizlabs/php_codesniffer",
"version": "3.10.0",
"version": "3.10.2",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
"reference": "57e09801c2fbae2d257b8b75bebb3deeb7e9deb2"
"reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/57e09801c2fbae2d257b8b75bebb3deeb7e9deb2",
"reference": "57e09801c2fbae2d257b8b75bebb3deeb7e9deb2",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017",
"reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017",
"shasum": ""
},
"require": {
@@ -1124,20 +1125,20 @@
"type": "open_collective"
}
],
"time": "2024-05-20T08:11:32+00:00"
"time": "2024-07-21T23:26:44+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.29.0",
"version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "21bd091060673a1177ae842c0ef8fe30893114d2"
"reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2",
"reference": "21bd091060673a1177ae842c0ef8fe30893114d2",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1",
"reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1",
"shasum": ""
},
"require": {
@@ -1184,7 +1185,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0"
},
"funding": [
{
@@ -1200,20 +1201,20 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-05-31T15:07:36+00:00"
},
{
"name": "szepeviktor/phpstan-wordpress",
"version": "v1.3.4",
"version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/szepeviktor/phpstan-wordpress.git",
"reference": "891d0767855a32c886a439efae090408cc1fa156"
"reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/891d0767855a32c886a439efae090408cc1fa156",
"reference": "891d0767855a32c886a439efae090408cc1fa156",
"url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/7f8cfe992faa96b6a33bbd75c7bace98864161e7",
"reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7",
"shasum": ""
},
"require": {
@@ -1228,7 +1229,8 @@
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpstan/phpstan-strict-rules": "^1.2",
"phpunit/phpunit": "^8.0 || ^9.0",
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8"
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0",
"wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
},
"suggest": {
"swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods"
@@ -1260,9 +1262,9 @@
],
"support": {
"issues": "https://github.com/szepeviktor/phpstan-wordpress/issues",
"source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.4"
"source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.5"
},
"time": "2024-03-21T16:32:59+00:00"
"time": "2024-06-28T22:27:19+00:00"
},
{
"name": "wp-coding-standards/wpcs",
+1 -1
View File
@@ -201,7 +201,7 @@ class Customers {
* @return array
*/
public static function upgrade_models( $connection, $resolver ) {
if ( 'customers' === $resolver->get_info()->fieldName ) { // @phpstan-ignore-line
if ( 'customers' === $resolver->get_info()->fieldName ) {
$nodes = [];
$edges = [];
foreach ( $connection['nodes'] as $node ) {
+2 -2
View File
@@ -99,7 +99,7 @@ class Orders {
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WC_Customer $customer Customer object of querying user.
*
* @return array
* @return array|\GraphQL\Deferred
*/
private static function get_customer_order_connection( $resolver, $customer ) {
// If not "billing email" or "ID" set bail early by returning an empty connection.
@@ -129,7 +129,7 @@ class Orders {
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WC_Customer $customer Customer object of querying user.
*
* @return array
* @return array|\GraphQL\Deferred
*/
private static function get_customer_refund_connection( $resolver, $customer ) {
$empty_results = [
@@ -64,10 +64,10 @@ class Cart_Item_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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.
* @param array $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @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.
* @param array<string, mixed>|null $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*/
$query_args = apply_filters( 'graphql_cart_item_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -159,10 +159,10 @@ class Coupon_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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
* @param array $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @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
* @param array<string, mixed>|null $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
*/
$query_args = apply_filters( 'graphql_coupon_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -178,7 +178,9 @@ class Coupon_Connection_Resolver extends AbstractConnectionResolver {
* @return \WP_Query
*/
public function get_query() {
$query = new \WP_Query( $this->query_args );
/** @var array $query_args */
$query_args = $this->query_args;
$query = new \WP_Query( $query_args );
if ( isset( $query->query_vars['suppress_filters'] ) && true === $query->query_vars['suppress_filters'] ) {
throw new InvariantViolation( __( 'WP_Query has been modified by a plugin or theme to suppress_filters, which will cause issues with WPGraphQL Execution. If you need to suppress filters for a specific reason within GraphQL, consider registering a custom field to the WPGraphQL Schema with a custom resolver.', 'wp-graphql-woocommerce' ) );
@@ -237,13 +239,13 @@ class Coupon_Connection_Resolver extends AbstractConnectionResolver {
* 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)
* @param \WPGraphQL\AppContext $context The AppContext object
* @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<string, mixed>|null $all_args All of the arguments for the query (not just the "where" args)
* @param \WPGraphQL\AppContext $context The AppContext object
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object
* @param mixed|string|array $post_type The post type for the query
* @param mixed|string|array $post_type The post type for the query
*/
$args = apply_filters(
'graphql_map_input_fields_to_coupon_query',
@@ -91,10 +91,10 @@ class Downloadable_Item_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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.
* @param array $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @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.
* @param array<string, mixed>|null $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*/
$query_args = apply_filters( 'graphql_downloadable_item_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -182,10 +182,10 @@ class Order_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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
* @param array $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @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
* @param array<string, mixed>|null $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
*/
$query_args = apply_filters( 'graphql_order_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -201,7 +201,9 @@ class Order_Connection_Resolver extends AbstractConnectionResolver {
* @return \WC_Order_Query
*/
public function get_query() {
$query = new \WC_Order_Query( $this->query_args );
/** @var array<string, mixed> $query_args */
$query_args = $this->query_args;
$query = new \WC_Order_Query( $query_args );
if ( true === $query->get( 'suppress_filters', false ) ) {
throw new InvariantViolation( __( 'WC_Order_Query has been modified by a plugin or theme to suppress_filters, which will cause issues with WPGraphQL Execution. If you need to suppress filters for a specific reason within GraphQL, consider registering a custom field to the WPGraphQL Schema with a custom resolver.', 'wp-graphql-woocommerce' ) );
@@ -335,13 +337,13 @@ class Order_Connection_Resolver extends AbstractConnectionResolver {
* 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)
* @param \WPGraphQL\AppContext $context The AppContext object
* @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<string, mixed>|null $all_args All of the arguments for the query (not just the "where" args)
* @param \WPGraphQL\AppContext $context The AppContext object
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object
* @param mixed|string|array $post_type The post type for the query
* @param mixed|string|array $post_type The post type for the query
*/
$args = apply_filters(
'graphql_map_input_fields_to_order_query',
@@ -45,10 +45,10 @@ class Order_Item_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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.
* @param array $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @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.
* @param array<string, mixed>|null $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*/
$query_args = apply_filters( 'graphql_order_item_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -80,10 +80,10 @@ class Order_Item_Connection_Resolver extends AbstractConnectionResolver {
/**
* Filter the $item_type to allow non-core item types.
*
* @param string $item_type Order item type.
* @param mixed $source The source that's passed down the GraphQL queries.
* @param array $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @param string $item_type Order item type.
* @param mixed $source The source that's passed down the GraphQL queries.
* @param array<string, mixed>|null $args The inputArgs on the field.
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree.
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*/
$type = apply_filters(
@@ -143,7 +143,7 @@ class Order_Item_Connection_Resolver extends AbstractConnectionResolver {
// Cache items for later.
foreach ( $items as $item ) {
$this->loader->prime(
$this->get_loader()->prime(
$item->get_id(),
new \WPGraphQL\WooCommerce\Model\Order_Item( $item, $this->source )
);
@@ -10,7 +10,7 @@
namespace WPGraphQL\WooCommerce\Data\Connection;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery;
use WPGraphQL\Data\Connection\AbstractConnectionResolver;
use WPGraphQL\Utils\Utils;
use WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce;
@@ -36,9 +36,9 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
/**
* The instance of the class that helps filtering with the product attributes lookup table.
*
* @var \Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer
* @var \Automattic\WooCommerce\StoreApi\Utilities\ProductQuery
*/
private $filterer; // @phpstan-ignore-line
private $products_query;
/**
* Refund_Connection_Resolver constructor.
@@ -52,7 +52,7 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
// @codingStandardsIgnoreLine.
$this->post_type = ['product'];
$this->filterer = wc_get_container()->get( Filterer::class ); // @phpstan-ignore-line
$this->products_query = new ProductQuery();
/**
* Call the parent construct to setup class data
@@ -155,7 +155,7 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
/**
* If the query contains search default the results to
*/
if ( isset( $query_args['search'] ) && ! empty( $query_args['search'] ) ) {
if ( ! empty( $query_args['search'] ) && empty( $query_args['orderby'] ) ) {
/**
* Don't order search results by title (causes funky issues with cursors)
*/
@@ -167,6 +167,54 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
$query_args = array_merge( $query_args, \WC()->query->get_catalog_ordering_args( 'menu_order', isset( $last ) ? 'ASC' : 'DESC' ) );
}
$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;
}
}
if ( $offset_product && 'popularity' === $query_args['orderby'] ) {
$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';
}
if ( $offset_product && 'rating' === $query_args['orderby'] ) {
$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';
}
if ( $offset_product && 'comment_count' === $query_args['orderby'] ) {
$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';
}
/**
* 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
@@ -178,7 +226,7 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
*
* @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
* @param array $args The inputArgs on the field
* @param array<string, mixed>|null $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
*/
@@ -189,83 +237,19 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
* {@inheritDoc}
*/
public function get_query() {
// Run query and add product query filters.
$wp_query = new \WP_Query();
$wp_query->query_vars = wp_parse_args( $this->query_args );
add_filter( 'posts_clauses', [ $this, 'product_query_post_clauses' ], 10, 2 );
add_filter( 'posts_clauses', [ $this->products_query, 'add_query_clauses' ], 10, 2 );
return $wp_query;
}
/**
* Filter the product query and apply WC's custom clauses.
*
* @param array $args The query clauses.
* @param \WP_Query $wp_query The WP_Query object.
*
* @return array
*/
public function product_query_post_clauses( $args, $wp_query ) {
if ( 'product_query' !== $wp_query->get( 'wc_query' ) ) {
return $args;
}
$args = $this->price_filter_post_clauses( $args, $wp_query );
$args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, [] ); // @phpstan-ignore-line
return $args;
}
/**
* Custom query used to filter products by price.
*
* @param array $args SQL clauses.
* @param \WP_Query $wp_query WP_Query object.
*
* @return array
*/
public function price_filter_post_clauses( $args, $wp_query ) {
global $wpdb;
$min_price = $wp_query->get( 'min_price' );
$max_price = $wp_query->get( 'max_price' );
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$current_min_price = $min_price ?: 0;
$current_max_price = $max_price ?: PHP_INT_MAX;
// phpcs:enable WordPress.Security.NonceVerification.Recommended
/**
* Adjust if the store taxes are not displayed how they are stored.
* Kicks in when prices excluding tax are displayed including tax.
*/
if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
$tax_class = apply_filters( 'woocommerce_price_filter_widget_tax_class', '' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$tax_rates = \WC_Tax::get_rates( $tax_class );
if ( $tax_rates ) {
$current_min_price -= \WC_Tax::get_tax_total( \WC_Tax::calc_inclusive_tax( $current_min_price, $tax_rates ) );
$current_max_price -= \WC_Tax::get_tax_total( \WC_Tax::calc_inclusive_tax( $current_max_price, $tax_rates ) );
}
}
$args['join'] .= ! strstr( $args['join'], 'wc_product_meta_lookup' )
? " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "
: '';
$args['where'] .= $wpdb->prepare(
' AND NOT (%f<wc_product_meta_lookup.min_price OR %f>wc_product_meta_lookup.max_price ) ',
$current_max_price,
$current_min_price
);
return $args;
return new \WP_Query();
}
/**
* {@inheritDoc}
*/
public function get_ids_from_query() {
$ids = $this->query->get_posts();
remove_filter( 'posts_clauses', [ $this, 'product_query_post_clauses' ], 10 );
// Run query and get IDs.
$ids = $this->query->query( $this->query_args );
remove_filter( 'posts_clauses', [ $this->products_query, 'add_query_clauses' ], 10 );
// If we're going backwards, we need to reverse the array.
if ( ! empty( $this->args['last'] ) ) {
@@ -332,9 +316,13 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
$default_order = isset( $this->args['last'] ) ? 'ASC' : 'DESC';
$orderby_input = current( $where_args['orderby'] );
$orderby = $orderby_input['field'];
$order = ! empty( $orderby_input['order'] ) ? $orderby_input['order'] : $default_order;
$query_args = array_merge( $query_args, \WC()->query->get_catalog_ordering_args( $orderby, $order ) );
$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 ) );
}
if ( isset( $where_args['includeVariations'] ) && $where_args['includeVariations'] ) {
@@ -669,15 +657,11 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
];
}
if ( ! empty( $where_args['minPrice'] ) ) {
$query_args['min_price'] = floatval( $where_args['minPrice'] );
}
if ( ! empty( $where_args['minPrice'] ) ) {
$query_args['min_price'] = floatval( $where_args['minPrice'] );
$query_args['min_price'] = str_replace( '.', '', number_format( $where_args['minPrice'], 2 ) );
}
if ( ! empty( $where_args['maxPrice'] ) ) {
$query_args['max_price'] = floatval( $where_args['maxPrice'] );
$query_args['max_price'] = str_replace( '.', '', number_format( $where_args['maxPrice'], 2 ) );
}
if ( isset( $where_args['stockStatus'] ) ) {
@@ -64,7 +64,7 @@ class Shipping_Method_Connection_Resolver extends AbstractConnectionResolver {
}
foreach ( $methods as $method ) {
$this->loader->prime( $method->id, new Shipping_Method( $method ) );
$this->get_loader()->prime( $method->id, new Shipping_Method( $method ) );
}
// Get shipping method IDs.
@@ -74,7 +74,7 @@ class Tax_Class_Connection_Resolver extends AbstractConnectionResolver {
// Cache cart items for later.
foreach ( $tax_classes as $tax_class ) {
$this->loader->prime( $tax_class['slug'], $tax_class );
$this->get_loader()->prime( $tax_class['slug'], $tax_class );
}
return wp_list_pluck( $tax_classes, 'slug' );
@@ -95,10 +95,10 @@ class Tax_Rate_Connection_Resolver extends AbstractConnectionResolver {
/**
* 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
* @param array $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @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
* @param array<string, mixed>|null $args The inputArgs on the field
* @param \WPGraphQL\AppContext $context The AppContext passed down the GraphQL tree
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
*/
$query_args = apply_filters( 'graphql_tax_rate_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
@@ -114,8 +114,11 @@ class Tax_Rate_Connection_Resolver extends AbstractConnectionResolver {
public function get_query() {
global $wpdb;
if ( ! empty( $this->query_args['where'] ) ) {
$sql_where = $this->query_args['where'];
/** @var array<string, mixed> $query_args */
$query_args = $this->query_args;
if ( ! empty( $query_args['where'] ) ) {
$sql_where = $query_args['where'];
$results = $wpdb->get_results( // @codingStandardsIgnoreStart
$wpdb->prepare(
@@ -126,8 +129,8 @@ class Tax_Rate_Connection_Resolver extends AbstractConnectionResolver {
WHERE {$sql_where}
GROUP BY rates.tax_rate_id
ORDER BY %s %s",
$this->query_args['orderby'],
$this->query_args['order']
$query_args['orderby'],
$query_args['order']
)
); // @codingStandardsIgnoreEnd
} else {
@@ -136,8 +139,8 @@ class Tax_Rate_Connection_Resolver extends AbstractConnectionResolver {
"SELECT tax_rate_id
FROM {$wpdb->prefix}woocommerce_tax_rates
ORDER BY %s %s",
$this->query_args['orderby'],
$this->query_args['order']
$query_args['orderby'],
$query_args['order']
)
); // @codingStandardsIgnoreEnd
}//end if
+1 -1
View File
@@ -74,6 +74,7 @@ class WC_CPT_Loader extends AbstractDataLoader {
* @throws \GraphQL\Error\UserError - throws if the post-type is not a valid WooCommerce post-type.
*/
public function loadKeys( array $keys ) {
/** @var array<int> $keys */
if ( empty( $keys ) ) {
return $keys;
}
@@ -177,7 +178,6 @@ class WC_CPT_Loader extends AbstractDataLoader {
break;
case 'product_variation':
case 'shop_refund':
$parent_id = $entry->post_parent;
if ( ! empty( $entry->post_parent ) ) {
$context->get_loader( 'wc_post' )->load_deferred( $entry->post_parent );
}
+1
View File
@@ -172,6 +172,7 @@ class Product extends WC_Post {
}
sort( $prices, SORT_NUMERIC );
graphql_debug( implode( ', ', $prices ) );
if ( $raw ) {
return implode( ', ', $prices );
-4
View File
@@ -10,7 +10,6 @@
namespace WPGraphQL\WooCommerce\Mutation;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\Model\Comment;
@@ -105,9 +104,6 @@ class Review_Write {
$resolver = CommentCreate::mutate_and_get_payload();
$payload = $resolver( $input, $context, $info );
if ( is_a( $payload, UserError::class ) ) {
throw $payload;
}
// Set product rating upon successful creation of the review.
if ( $payload['success'] ) {
@@ -35,23 +35,21 @@ class Products_Orderby_Enum extends Post_Type_Orderby_Enum {
'description' => __( 'Order by product\'s current price', 'wp-graphql-woocommerce' ),
],
'REGULAR_PRICE' => [
'value' => 'regular_price',
'description' => __( 'Order by product\'s regular price', 'wp-graphql-woocommerce' ),
'value' => 'price',
'description' => __( 'Order by product\'s regular price', 'wp-graphql-woocommerce' ),
'deprecationReason' => __( 'This field is deprecated and will be removed in a future version. Use "PRICE" instead.', 'wp-graphql-woocommerce' ),
],
'SALE_PRICE' => [
'value' => 'sale_price',
'description' => __( 'Order by product\'s sale price', 'wp-graphql-woocommerce' ),
],
'RELEVANCE' => [
'value' => 'relevance',
'description' => __( 'Order by relevance', 'wp-graphql-woocommerce' ),
'value' => 'price',
'description' => __( 'Order by product\'s sale price', 'wp-graphql-woocommerce' ),
'deprecationReason' => __( 'This field is deprecated and will be removed in a future version. Use "PRICE" instead.', 'wp-graphql-woocommerce' ),
],
'POPULARITY' => [
'value' => 'popularity',
'description' => __( 'Order by product popularity', 'wp-graphql-woocommerce' ),
],
'REVIEW_COUNT' => [
'value' => 'popularity',
'value' => 'comment_count',
'description' => __( 'Order by number of reviews on product', 'wp-graphql-woocommerce' ),
],
'RATING' => [
@@ -59,16 +57,19 @@ class Products_Orderby_Enum extends Post_Type_Orderby_Enum {
'description' => __( 'Order by product average rating', 'wp-graphql-woocommerce' ),
],
'ON_SALE_FROM' => [
'value' => '_sale_price_dates_from',
'description' => __( 'Order by date product sale starts', 'wp-graphql-woocommerce' ),
'value' => 'date',
'description' => __( 'Order by date product sale starts', 'wp-graphql-woocommerce' ),
'deprecationReason' => __( 'This field is deprecated and will be removed in a future version.', 'wp-graphql-woocommerce' ),
],
'ON_SALE_TO' => [
'value' => '_sale_price_dates_to',
'description' => __( 'Order by date product sale ends', 'wp-graphql-woocommerce' ),
'value' => 'date',
'description' => __( 'Order by date product sale ends', 'wp-graphql-woocommerce' ),
'deprecationReason' => __( 'This field is deprecated and will be removed in a future version.', 'wp-graphql-woocommerce' ),
],
'TOTAL_SALES' => [
'value' => 'total_sales',
'description' => __( 'Order by total sales of products sold', 'wp-graphql-woocommerce' ),
'value' => 'popularity',
'description' => __( 'Order by total sales of products sold', 'wp-graphql-woocommerce' ),
'deprecationReason' => __( 'This field is deprecated and will be removed in a future version. Use "POPULARITY" instead', 'wp-graphql-woocommerce' ),
],
]
)
@@ -61,6 +61,8 @@ class Product_With_Pricing {
// @codingStandardsIgnoreLine.
return $source->priceRaw;
} else {
graphql_debug( $source->price );
// @codingStandardsIgnoreLine.
return $source->price;
}
},
+1 -1
View File
@@ -427,7 +427,7 @@ class Order_Type {
* @param \WPGraphQL\AppContext $context AppContext instance.
* @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
*
* @return array
* @return \GraphQL\Deferred
*/
public static function resolve_item_connection( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new Order_Item_Connection_Resolver( $source, $args, $context, $info );
+8 -6
View File
@@ -589,6 +589,7 @@ class Root_Query {
],
'description' => __( 'Statistics for a product taxonomy query', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $_, $args ) {
/** @var array<string, mixed> $data */
$data = [
'min_price' => null,
'max_price' => null,
@@ -596,7 +597,7 @@ class Root_Query {
'stock_status_counts' => null,
'rating_counts' => null,
];
$filters = new ProductQueryFilters(); // @phpstan-ignore-line
$filters = new ProductQueryFilters();
// Process client-side filters.
$request = Collection_Stats_Type::prepare_rest_request( $args['where'] ?? [] );
@@ -633,7 +634,8 @@ class Root_Query {
$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
/** @var object{min_price: float, max_price: float} */
$price_results = $filters->get_filtered_price( $filter_request );
$data['min_price'] = $price_results->min_price;
$data['max_price'] = $price_results->max_price;
}
@@ -645,7 +647,7 @@ class Root_Query {
* @var \WP_REST_Request $filter_request
*/
$filter_request = clone $request;
$counts = $filters->get_stock_status_counts( $filter_request ); // @phpstan-ignore-line
$counts = $filters->get_stock_status_counts( $filter_request );
$data['stock_status_counts'] = [];
@@ -693,7 +695,7 @@ class Root_Query {
}
$filter_request->set_param( 'attributes', $filter_attributes );
$counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] ); // @phpstan-ignore-line
$counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] );
$data['attribute_counts'][ $taxonomy ] = [];
foreach ( $counts as $key => $value ) {
@@ -707,7 +709,7 @@ class Root_Query {
}
if ( ! empty( $taxonomy__and_queries ) ) {
$counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries ); // @phpstan-ignore-line
$counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries );
foreach ( $taxonomy__and_queries as $taxonomy ) {
$data['attribute_counts'][ $taxonomy ] = [];
@@ -729,7 +731,7 @@ class Root_Query {
* @var \WP_REST_Request $filter_request
*/
$filter_request = clone $request;
$counts = $filters->get_rating_counts( $filter_request ); // @phpstan-ignore-line
$counts = $filters->get_rating_counts( $filter_request );
$data['rating_counts'] = [];
foreach ( $counts as $key => $value ) {
@@ -5,7 +5,8 @@ use Tests\WPGraphQL\Logger\CodeceptLogger as Signal;
class CartTransactionQueueCest {
private $product_catalog;
public function _before( FunctionalTester $I ) {
public function _before( FunctionalTester $I, $scenario ) {
$scenario->skip( 'This test is unstable' );
// Create Products
$this->product_catalog = $I->getCatalog();
}
-473
View File
@@ -439,479 +439,6 @@ class ProductQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
$this->assertQuerySuccessful( $response, $expected );
}
public function testProductsQueryAndWhereArgs() {
$category_3 = $this->factory->product->createProductCategory( 'category-three' );
$category_4 = $this->factory->product->createProductCategory( 'category-four' );
$product_ids = [
$this->factory->product->createSimple(
[
'slug' => 'test-product-1',
'price' => 6000,
'regular_price' => 6000,
]
),
$this->factory->product->createSimple(
[
'price' => 2,
'regular_price' => 2,
'category_ids' => [ $category_3, $category_4 ],
]
),
$this->factory->product->createSimple(
[
'featured' => 'true',
'category_ids' => [ $category_3 ],
]
),
$this->factory->product->createExternal(),
$this->factory->product->createSimple(
[
'price' => 200,
'regular_price' => 300,
'sale_price' => 200,
'date_on_sale_from' => ( new \DateTime( 'yesterday' ) )->format( 'Y-m-d H:i:s' ),
'date_on_sale_to' => ( new \DateTime( 'tomorrow' ) )->format( 'Y-m-d H:i:s' ),
'stock_status' => 'outofstock',
]
),
];
$query = '
query (
$slugIn: [String],
$status: String,
$category: String,
$categoryIn: [String],
$categoryNotIn: [String],
$categoryId: Int,
$categoryIdIn: [Int]
$categoryIdNotIn: [Int]
$type: ProductTypesEnum,
$typeIn: [ProductTypesEnum],
$typeNotIn: [ProductTypesEnum],
$featured: Boolean,
$maxPrice: Float,
$orderby: [ProductsOrderbyInput]
$taxonomyFilter: ProductTaxonomyInput
$include: [Int]
$exclude: [Int]
$stockStatus: [StockStatusEnum]
) {
products( where: {
slugIn: $slugIn,
status: $status,
category: $category,
categoryIn: $categoryIn,
categoryNotIn: $categoryNotIn,
categoryId: $categoryId,
categoryIdIn: $categoryIdIn,
categoryIdNotIn: $categoryIdNotIn,
type: $type,
typeIn: $typeIn,
typeNotIn: $typeNotIn,
featured: $featured,
maxPrice: $maxPrice,
orderby: $orderby,
taxonomyFilter: $taxonomyFilter
include: $include
exclude: $exclude
stockStatus: $stockStatus
} ) {
nodes {
id
... on ProductWithPricing {
databaseId
price
}
... on InventoriedProduct {
stockStatus
}
}
}
}
';
$all_expected_product_nodes = array_map(
function ( $product_id ) {
return $this->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_id ) ) ]
);
},
$product_ids
);
/**
* Assertion One
*
* Tests query with no arguments, and expect all products to be returned.
*/
$response = $this->graphql( compact( 'query' ) );
$this->assertQuerySuccessful( $response, $all_expected_product_nodes );
/**
* Assertion Two
*
* Tests query with "slug" where argument, and expect the product with
* the slug "test-product-1" to be returned.
*/
$variables = [ 'slugIn' => [ 'test-product-1' ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids ) {
$product = \wc_get_product( $product_ids[ $index ] );
return 'test-product-1' === $product->get_slug();
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Three
*
* Tests query with "status" where argument, and expect the products with
* a status of "pending" to be returned, which there are none among the test
* product with that status.
*/
$variables = [ 'status' => 'pending' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [ $this->expectedField( 'products.nodes', [] ) ];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Four
*
* Tests query with "type" where argument, and expect only "simple" products
* to be returned.
*/
$variables = [ 'type' => 'SIMPLE' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids ) {
$product = \wc_get_product( $product_ids[ $index ] );
return 'simple' === $product->get_type();
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Five
*
* Tests query with "typeIn" where argument, and expect only "simple" products
* to be returned.
*/
$variables = [ 'typeIn' => [ 'SIMPLE' ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// No need to reassign the $expected for this assertion.
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Six
*
* Tests query with "typeNotIn" where argument, and expect all types of products
* with except "simple" to be returned.
*/
$variables = [ 'typeNotIn' => [ 'SIMPLE' ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids ) {
$product = \wc_get_product( $product_ids[ $index ] );
return 'simple' !== $product->get_type();
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Seven
*
* Tests query with "featured" where argument, expect only featured products
* to be returned.
*/
$variables = [ 'featured' => true ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids ) {
$product = \wc_get_product( $product_ids[ $index ] );
return $product->get_featured();
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Eight
*
* Tests query with "maxPrice" where argument, and expect all product
* with a price of 10.00+ to be returned.
*/
$variables = [ 'maxPrice' => 10.00 ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids ) {
$product = \wc_get_product( $product_ids[ $index ] );
return 10.00 >= floatval( $product->get_price() );
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Nine
*
* Tests query with "orderby" where argument, and expect products to
* be return in descending order by "price".
*/
$variables = [
'orderby' => [
[
'field' => 'PRICE',
'order' => 'DESC',
],
],
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[0] ) ) ],
0
),
$this->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[1] ) ) ],
4
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Ten
*
* Tests query with "category" where argument, and expect products in
* the "category-three" category to be returned.
*/
$variables = [ 'category' => 'category-three' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids, $category_3 ) {
$product = \wc_get_product( $product_ids[ $index ] );
return in_array( $category_3, $product->get_category_ids(), true );
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
$this->clearLoaderCache( 'wc_post' );
/**
* Assertion Eleven
*
* Tests query with "categoryIn" where argument, and expect products in
* the "category-three" category to be returned.
*/
$variables = [ 'categoryIn' => [ 'category-three' ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// No need to reassign the $expected for this assertion.
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Twelve
*
* Tests query with "categoryId" where argument, and expect products in
* the "category-three" category to be returned.
*/
$variables = [ 'categoryId' => $category_3 ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// No need to reassign the $expected for this assertion either.
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Thirteen
*
* Tests query with "categoryNotIn" where argument, and expect all products
* except products in the "category-four" category to be returned.
*/
$variables = [ 'categoryNotIn' => [ 'category-four' ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids, $category_4 ) {
$product = \wc_get_product( $product_ids[ $index ] );
return ! in_array( $category_4, $product->get_category_ids(), true );
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Fourteen
*
* Tests query with "categoryIdNotIn" where argument, and expect all products
* except products in the "category-four" category to be returned.
*/
$variables = [ 'categoryIdNotIn' => [ $category_4 ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// No need to reassign the $expected for this assertion.
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Fifteen
*
* Tests query with "categoryIdIn" where argument, and expect products in
* the "category-four" category to be returned.
*/
$variables = [ 'categoryIdIn' => [ $category_4 ] ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids, $category_4 ) {
$product = \wc_get_product( $product_ids[ $index ] );
return in_array( $category_4, $product->get_category_ids(), true );
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Sixteen
*
* Tests "taxonomyFilter" where argument
*/
$variables = [
'taxonomyFilter' => [
'relation' => 'AND',
'filters' => [
[
'taxonomy' => 'PRODUCT_CAT',
'terms' => [ 'category-three' ],
],
[
'taxonomy' => 'PRODUCT_CAT',
'terms' => [ 'category-four' ],
'operator' => 'NOT_IN',
],
],
],
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = array_filter(
$all_expected_product_nodes,
static function ( $node, $index ) use ( $product_ids, $category_4, $category_3 ) {
$product = \wc_get_product( $product_ids[ $index ] );
return ! in_array( $category_4, $product->get_category_ids(), true )
&& in_array( $category_3, $product->get_category_ids(), true );
},
ARRAY_FILTER_USE_BOTH
);
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion 17-18
*
* Tests "include" where argument
*/
$variables = [
'include' => [ $product_ids[0] ],
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[0] ) ) ]
),
];
$this->assertQuerySuccessful( $response, $expected );
$variables = [
'include' => [ 1000 ],
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField(
'products.nodes',
self::IS_FALSY
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion 19-20
*
* Tests "exclude" where argument
*/
$variables = [
'exclude' => [ $product_ids[0] ],
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->not()->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[0] ) ) ]
),
];
$this->assertQuerySuccessful( $response, $expected );
$variables = [ 'exclude' => $product_ids ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField(
'products.nodes',
self::IS_FALSY
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion 21-22
*
* Tests "stockStatus" where argument
*/
$variables = [ 'stockStatus' => 'IN_STOCK' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->not()->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[4] ) ) ]
),
];
$this->assertQuerySuccessful( $response, $expected );
$variables = [ 'stockStatus' => 'OUT_OF_STOCK' ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'products.nodes',
[ $this->expectedField( 'id', $this->toRelayId( 'product', $product_ids[4] ) ) ],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
}
public function testProductToTermConnection() {
$test_category = $this->factory->product->createProductCategory( 'test-product-category-1' );
$test_tag = $this->factory->product->createProductTag( 'test-product-tag-1' );
@@ -170,96 +170,6 @@ class ProductVariationQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\
$this->assertQuerySuccessful( $response, $expected );
}
public function testVariationsQueryAndWhereArgs() {
// Create product variations.
$products = $this->factory->product_variation->createSome(
$this->factory->product->createVariable()
);
$variation_id = $products['variations'][0];
$id = $this->toRelayId( 'product', $products['product'] );
$product = wc_get_product( $products['product'] );
$variations = $products['variations'];
$prices = $product->get_variation_prices( true );
$query = '
query (
$id: ID!,
$minPrice: Float,
$parent: Int,
$parentIn: [Int],
$parentNotIn: [Int]
) {
product( id: $id ) {
... on VariableProduct {
price
regularPrice
salePrice
variations( where: {
minPrice: $minPrice,
parent: $parent,
parentIn: $parentIn,
parentNotIn: $parentNotIn
} ) {
nodes {
id
price
}
}
}
}
}
';
/**
* Assertion One
*
* Test query with no arguments
*/
$this->loginAsShopManager();
$variables = [ 'id' => $id ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[0] ) ),
$this->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[1] ) ),
$this->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[2] ) ),
$this->expectedField(
'product.price',
\wc_graphql_price( current( $prices['price'] ) )
. ' - '
. \wc_graphql_price( end( $prices['price'] ) )
),
$this->expectedField(
'product.regularPrice',
\wc_graphql_price( current( $prices['regular_price'] ) )
. ' - '
. \wc_graphql_price( end( $prices['regular_price'] ) )
),
$this->expectedField( 'product.salePrice', self::IS_NULL ),
];
$this->assertQuerySuccessful( $response, $expected );
$this->clearLoaderCache( 'wc_post' );
/**
* Assertion Two
*
* Test "minPrice" where argument
*/
$variables = [
'id' => $id,
'minPrice' => 15,
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->not()->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[0] ) ),
$this->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[1] ) ),
$this->expectedField( 'product.variations.nodes.#.id', $this->toRelayId( 'product_variation', $variations[2] ) ),
];
$this->assertQuerySuccessful( $response, $expected );
}
public function testProductVariationToMediaItemConnections() {
// Create product variations.
$products = $this->factory->product_variation->createSome(
File diff suppressed because it is too large Load Diff