fix: product variation duplicate fields removed (#957)

* fix: product variation duplicate fields removed

* chore: Linter compliances met

* chore: Linter compliances met

* chore: Linter compliances met

* chore: Linter compliances met

* chore: Linter compliances met

* chore: Linter compliances met

* devops: CI compliances met

* devops: unneeded access function removed

* devops: unneeded access function removed

* devops: unneeded access function removed
This commit is contained in:
Geoff Taylor
2026-01-16 12:28:23 -05:00
committed by GitHub
parent bde70cb72e
commit ef53404fc3
45 changed files with 526 additions and 500 deletions
+13
View File
@@ -52,6 +52,19 @@ wp config create \
--quiet \
--allow-root
# Install WP if not yet installed
if ! $( wp core is-installed --allow-root ); then
echo "Installing WordPress..."
wp core install \
--path="${WP_ROOT_FOLDER}" \
--url="${WORDPRESS_URL}" \
--title='Test' \
--admin_user="${ADMIN_USERNAME}" \
--admin_password="${ADMIN_PASSWORD}" \
--admin_email="${ADMIN_EMAIL}" \
--allow-root
fi
# Use alternative database export for WordPress 6.8+ to avoid MariaDB SSL issues
if [[ "${WP_VERSION}" == "6.8"* ]]; then
echo "Using alternative database export method for WordPress 6.8+"
Generated
+6 -6
View File
@@ -2046,16 +2046,16 @@
},
{
"name": "symfony/string",
"version": "v6.4.26",
"version": "v6.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea"
"reference": "50590a057841fa6bf69d12eceffce3465b9e32cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/5621f039a71a11c87c106c1c598bdcd04a19aeea",
"reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea",
"url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb",
"reference": "50590a057841fa6bf69d12eceffce3465b9e32cb",
"shasum": ""
},
"require": {
@@ -2111,7 +2111,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.4.26"
"source": "https://github.com/symfony/string/tree/v6.4.30"
},
"funding": [
{
@@ -2131,7 +2131,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T14:32:46+00:00"
"time": "2025-11-21T18:03:05+00:00"
},
{
"name": "szepeviktor/phpstan-wordpress",
+70
View File
@@ -0,0 +1,70 @@
<!--
title: "WPGraphQL for WooCommerce"
description: "Comprehensive documentation for WPGraphQL for WooCommerce (WooGraphQL) - the WPGraphQL extension that integrates WooCommerce with GraphQL for headless e-commerce solutions."
author: "Geoff Taylor"
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, headless, e-commerce"
-->
# WPGraphQL for WooCommerce
WPGraphQL for WooCommerce (WooGraphQL) is a free, open-source WordPress plugin that extends the WPGraphQL plugin, allowing you to access WooCommerce data through GraphQL queries and mutations.
## Table of Contents
### Getting Started
- [Installation](./installation.md)
- [Settings](./settings.md)
- [Configuring GraphQL Client For the User Session](./configuring-graphql-client-for-user-session.md)
- [Handling User Authentication](./handling-user-authentication.md)
### WooGraphQL
- [Routing by URI](./routing-by-uri.md)
- [Using Product Data](./using-product-data.md)
- [Creating Session Provider and using Cart Mutations](./handling-user-session-and-using-cart-mutations.md)
- [Using Cart Data](./using-cart-data.md)
- [Harmonizing with WordPress](./harmonizing-with-wordpress.md)
- [Using Checkout Mutation + Order Mutations](./using-checkout-mutation-and-order-mutations.md)
- [Using Order Data](./using-order-data.md)
- [Using Customer Data + Mutations](./using-customer-data-and-mutations.md)
### WooGraphQL Pro
For premium WooCommerce extension support (Subscriptions, Composite Products, Bundles, Add-ons), see the [WooGraphQL Pro Documentation](https://woographql.com/docs/woographql-pro).
### Contributing
- [Testing Quick-Start Guide](./testing-quick-start.md)
- [Local CLI Testing](./local-testing.md)
- [Development with Docker](./development-w-docker.md)
- [CLI Testing with Docker](./testing-w-docker.md)
## Introduction
WooGraphQL brings the power of GraphQL to WooCommerce, enabling developers to build modern, performant headless e-commerce applications. With WooGraphQL, you can:
- Query products, categories, tags, and attributes
- Manage shopping carts with session-based mutations
- Handle user authentication and customer accounts
- Process checkouts and manage orders
- Access WooCommerce settings and configurations
Whether you're building a React, Vue, or any other frontend application, WooGraphQL provides a flexible and efficient API to interact with your WooCommerce store.
### Key Features
- **Complete WooCommerce Integration**: Access all WooCommerce data types including products, orders, customers, coupons, and more
- **Session Management**: Built-in support for guest and authenticated user sessions
- **Cart Operations**: Full cart functionality with add, remove, update, and clear operations
- **Checkout Process**: Complete checkout mutation with payment gateway integration
- **Extensible**: Hooks and filters to customize the schema and responses
### Requirements
- WordPress 5.6+
- WooCommerce 6.0+
- WPGraphQL 1.14+
- PHP 7.4+
Get started by following the [Installation Guide](./installation.md).
@@ -1,9 +1,9 @@
---
<!--
title: "Configuring GraphQL Client for User Session"
description: "A step-by-step guide on how to configure your GraphQL client to handle user sessions, authentication, and more when working with WPGraphQL for WooCommerce and WPGraphQL."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, user session, authentication, configuration, client"
author: "Geoff Taylor"
---
-->
# Configuring a GraphQL Client for WooCommerce User Session Management
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Development w/ Docker"
author: "Geoff Taylor"
description: "A simple to follow guide on using the WPGraphQL for WooCommerce App Docker Image."
keywords: ""
---
-->
# Coming Soon
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Handling User Authentication with WooGraphQL"
description: "Learn how to handle user authentication in your headless WooCommerce application using WPGraphQL for WooCommerce and WPGraphQL for secure and seamless user experiences."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, user authentication, login, register, secure, headless"
author: "Geoff Taylor"
---
-->
# Handling User Authentication
@@ -1,9 +1,9 @@
---
<!--
title: "Handling User Session and Using Cart Mutations with WooGraphQL"
description: "Discover how to manage user sessions and perform cart mutations using WPGraphQL for WooCommerce and WPGraphQL in your headless WooCommerce application for a smooth shopping experience."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, user session, cart mutations, headless, shopping experience"
author: "Geoff Taylor"
---
-->
# Handling User Session and Using Cart Mutations
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Harmonizing with WordPress"
description: "Learn how to create a secure checkout button and understand the security behind the URL validation."
keywords: "WooGraphQL, WooCommerce, checkout functionality, secure checkout, session transferring URLs, nonce generation, session meta, URL validation, Client Session ID, UpdateSession mutation, GraphQL, cart page, WordPress, frontend security"
author: "Geoff Taylor"
---
-->
# Harmonizing with WordPress
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "WPGraphQL for WooCommerce Installation Guide"
author: "Geoff Taylor"
description: "Step-by-step instructions to install and set up WooGraphQL, the WPGraphQL extension that integrates WooCommerce with GraphQL for headless e-commerce solutions."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, installation, setup, headless e-commerce"
---
-->
# Installation
+3 -3
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Local Testing"
author: "Geoff Taylor"
description: "An extensive guide on local testing w/ WooGraphQL.",
description: "An extensive guide on local testing w/ WooGraphQL."
keywords: ""
---
-->
# Local CLI Testing
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Routing by URI with WooGraphQL"
description: "Discover how to implement routing by URI in your headless WooCommerce application using WPGraphQL for WooCommerce and WPGraphQL for efficient and user-friendly navigation."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, routing, URI, headless, navigation"
author: "Geoff Taylor"
---
-->
# Routing By URI
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "WPGraphQL for WooCommerce Settings Guide"
description: "Learn how to configure and manage WPGraphQL for WooCommerce settings to optimize the integration of WooCommerce with WPGraphQL for your headless e-commerce solution."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, settings, configuration, headless e-commerce"
author: "Geoff Taylor"
---
-->
# WPGraphQL for WooCommerce Settings
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Testing (Quick-Start Guide)"
author: "Geoff Taylor"
description: "A simple guide to get started testing with WooGraphQL."
keywords: ""
---
-->
# Testing (Quick-Start Guide)
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Testing w/ Docker"
author: "Geoff Taylor"
description: "An extensive guide on testing w/ WPGraphQL for WooCommerce + Docker."
keywords: ""
---
-->
# Coming Soon
+1 -4
View File
@@ -20,10 +20,7 @@
## WooGraphQL Pro
- [Using Subscription Data + Mutations](using-subscription-data-and-mutations.md)
- [Using Composite Product Data + Mutations](using-composite-product-data-and-mutations.md)
- [Using Product Bundle Data + Mutations](using-product-bundle-data-and-mutations.md)
- [Using Product Add-ons Data + Mutations](using-product-addons-data-and-mutations.md)
For documentation on using premium WooCommerce extensions with WooGraphQL, see the [WooGraphQL Pro Documentation](https://woographql.com/docs/woographql-pro).
## Extending WooGraphQL
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Using Cart Data with WooGraphQL"
description: "Learn how to retrieve and utilize cart data in your headless WooCommerce application with WPGraphQL for WooCommerce and WPGraphQL, enabling seamless shopping experiences for your customers."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, cart data, headless, shopping experience"
author: "Geoff Taylor"
---
-->
# Using Cart Data
@@ -1,9 +1,9 @@
---
<!--
title: "Using Checkout Mutation + Order Mutations with WooGraphQL"
description: "Learn how to handle repeat shoppers with existing payment methods using the `checkout` mutation, and side-stepping WooCommerce's management of the session completely by using the `createOrder` mutation."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, checkout mutation, createOrder mutation, session management"
author: "Geoff Taylor"
---
-->
# Using Checkout Mutation and Order Mutations
@@ -1,9 +1,9 @@
---
<!--
title: "Using Composite Product Data + Mutations with WooGraphQL"
description: "Learn how to use the Composite Product functionality with WPGraphQL for WooCommerce by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Composite Product functionality, Product Data, Session Provider, Cart Mutations"
author: "Geoff Taylor"
---
-->
# Using Composite Product Data + Mutations
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Using Customer Data + Mutations with WooGraphQL"
description: "Learn how to utilize the `customer` query and mutations by building a clone of a WooCommerce's user account pages in React.js."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, customer query, customer mutations, React.js, user account pages"
author: "Geoff Taylor"
---
-->
# Using Customer Data + Mutations
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Using Order Data with WooGraphQL"
description: "Learn how to utilize the Order queries and type by building a basic order status page in React.js that works by taking an `email` and returning a list of all the orders connected to that `email`."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Order queries, React.js, order status page"
author: "Geoff Taylor"
---
-->
# Using Order Data
@@ -1,9 +1,9 @@
---
<!--
title: "Using Product Add-ons Data + Mutations with WooGraphQL"
author: "Geoff Taylor"
description: "Learn how to use the Product Add-on functionality with WPGraphQL for WooCommerce by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Product Add-on functionality, Product Data, Session Provider, Cart Mutations"
---
-->
# Coming Soon
@@ -1,9 +1,9 @@
---
<!--
title: "Using Product Bundle Data + Mutations with WooGraphQL"
description: "Learn how to use the Product Bundle functionality with WPGraphQL for WooCommerce by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Product Bundle functionality, Product Data, Session Provider, Cart Mutations"
author: "Geoff Taylor"
---
-->
# Using Product Bundle Data + Mutations
+2 -2
View File
@@ -1,9 +1,9 @@
---
<!--
title: "Using Product Data with WooGraphQL"
description: "Learn how to efficiently fetch and use product data in your headless WooCommerce application using WPGraphQL for WooCommerce and WPGraphQL for a seamless shopping experience."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, product data, headless, shopping experience"
author: "Geoff Taylor"
---
-->
# Using Product Data
@@ -1,9 +1,9 @@
---
<!--
title: "Using Subscription Data + Mutations with WooGraphQL"
description: "Learn how to use the Subscription functionality provided by WooGraphQL Pro and build upon the code written in `Using Product Data` and `Routing by URI` by rewriting the `ProductListing` and `SingleProduct` components to support `SubscriptionProduct` types."
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Subscription functionality, ProductListing, SingleProduct, SubscriptionProduct types"
author: "Geoff Taylor"
---
-->
# Using Subscription Data + Mutations
+40 -17
View File
@@ -188,24 +188,47 @@ class JWT_Auth_Schema_Filters {
);
if ( ! WooCommerce_Filters::is_session_handler_disabled() ) {
register_graphql_field(
'LoginPayload',
'sessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function () {
/**
* Session Handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
$token_type = woographql_setting( 'set_session_token_type', 'legacy' );
if ( in_array( $token_type, [ 'legacy', 'both' ], true ) ) {
register_graphql_field(
'LoginPayload',
'sessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function () {
/**
* Session Handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
},
]
);
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
},
]
);
}
if ( in_array( $token_type, [ 'store-api', 'both' ], true ) ) {
register_graphql_field(
'LoginPayload',
'cartToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function () {
/**
* Session Handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return apply_filters( 'graphql_customer_session_token', $session->build_cart_token() );
},
]
);
}
}
}
}
+1 -1
View File
@@ -81,7 +81,7 @@ class Type_Registry {
Type\WPInterface\Attribute::register_interface();
Type\WPInterface\Product_Attribute::register_interface();
Type\WPInterface\Cart_Error::register_interface();
Type\WPInterface\Payment_Token::register_interface();
Type\WPInterface\Payment_Token_Interface::register_interface();
Type\WPInterface\Product_Union::register_interface();
Type\WPInterface\Cart_Item::register_interface();
Type\WPInterface\Downloadable_Product::register_interface();
+28 -1
View File
@@ -34,6 +34,9 @@ class WooCommerce_Filters {
add_filter( 'woocommerce_session_handler', [ self::class, 'woocommerce_session_handler' ] );
add_filter( 'graphql_response_headers_to_send', [ self::class, 'add_session_header_to_expose_headers' ] );
add_filter( 'graphql_access_control_allow_headers', [ self::class, 'add_session_header_to_allow_headers' ] );
// Initialize cart/session after JWT authentication has had a chance to run.
add_action( 'graphql_process_http_request', [ self::class, 'initialize_session_and_cart' ] );
}
// Add better support for Stripe payment gateway.
@@ -50,7 +53,31 @@ class WooCommerce_Filters {
* @return boolean
*/
public static function is_session_handler_disabled() {
return defined( 'NO_QL_SESSION_HANDLER' ) || 'on' === woographql_setting( 'disable_ql_session_handler', 'off' );
return \defined( 'NO_QL_SESSION_HANDLER' ) || 'on' === woographql_setting( 'disable_ql_session_handler', 'off' );
}
/**
* Initialize WooCommerce session and cart for GraphQL requests.
*
* This is hooked to 'graphql_before_execute' to ensure JWT authentication has
* had a chance to set the current user before the session is initialized.
* This fixes an issue where guest sessions weren't being updated when a user
* provides both a Cart-Token (session) and Authorization (JWT) header.
*
* @return void
*/
public static function initialize_session_and_cart() {
// Clear any existing WooCommerce objects to ensure fresh initialization
// with the correct user context after JWT authentication.
// @phpstan-ignore-next-line
\WC()->customer = null;
// @phpstan-ignore-next-line
\WC()->cart = null;
// @phpstan-ignore-next-line
\WC()->session = null;
wc_load_cart();
}
/**
+1 -1
View File
@@ -261,7 +261,7 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/interface/class-product-attribute.php';
require $include_directory_path . 'type/interface/class-product.php';
require $include_directory_path . 'type/interface/class-product-variation.php';
require $include_directory_path . 'type/interface/class-payment-token.php';
require $include_directory_path . 'type/interface/class-payment-token-interface.php';
require $include_directory_path . 'type/interface/class-product-union.php';
require $include_directory_path . 'type/interface/class-cart-item.php';
require $include_directory_path . 'type/interface/class-downloadable-product.php';
+101 -100
View File
@@ -12,6 +12,7 @@ namespace WPGraphQL\WooCommerce\Connection;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver;
use WPGraphQL\WooCommerce\Model\Customer;
/**
* Class - Orders
@@ -93,104 +94,6 @@ class Orders {
);
}
/**
* Returns order connection filter by customer.
*
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WC_Customer $customer Customer object of querying user.
*
* @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.
if ( empty( $customer->get_billing_email() ) && empty( $customer->get_id() ) ) {
return [
'nodes' => [],
'edges' => [],
];
}
$customer_id = $customer->get_id();
$billing_email = $customer->get_billing_email();
if ( ! empty( $customer_id ) ) {
$resolver->set_query_arg( 'customer_id', $customer_id );
$resolver->set_should_execute( \WC()->customer->get_id() === $customer_id );
} elseif ( ! empty( $billing_email ) ) {
$resolver->set_query_arg( 'billing_email', $billing_email );
$resolver->set_should_execute( \WC()->customer->get_billing_email() === $billing_email );
}
return $resolver->get_connection();
}
/**
* Returns refund connection filter by customer.
*
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WC_Customer $customer Customer object of querying user.
*
* @return array|\GraphQL\Deferred
*/
private static function get_customer_refund_connection( $resolver, $customer ) {
$empty_results = [
'pageInfo' => null,
'nodes' => [],
'edges' => [],
];
// If not "billing email" or "ID" set bail early by returning an empty connection.
if ( empty( $customer->get_billing_email() ) && empty( $customer->get_id() ) ) {
return $empty_results;
}
$order_ids = [];
$customer_id = $customer->get_id();
$billing_email = $customer->get_billing_email();
if ( ! empty( $customer_id ) ) {
$args = [
'customer_id' => $customer_id,
'return' => 'ids',
];
/** @var array<int> $order_ids_by_customer_id */
$order_ids_by_customer_id = wc_get_orders( $args );
if ( is_array( $order_ids_by_customer_id ) ) {
$order_ids = $order_ids_by_customer_id;
}
}
if ( ! empty( $billing_email ) ) {
$args = [
'billing_email' => $billing_email,
'return' => 'ids',
];
/** @var array<int> $order_ids_by_email */
$order_ids_by_email = wc_get_orders( $args );
// Merge the arrays of order IDs.
if ( is_array( $order_ids_by_email ) ) {
$order_ids = array_merge( $order_ids, $order_ids_by_email );
}
}
// If no orders found, return empty connection.
if ( empty( $order_ids ) ) {
return $empty_results;
}
// Remove duplicates.
$order_ids = array_unique( $order_ids );
// Set connection args.
$resolver->set_should_execute(
( 0 !== $customer_id && \WC()->customer->get_id() === $customer_id )
|| \WC()->customer->get_billing_email() === $billing_email
);
$resolver->set_query_arg( 'post_parent__in', array_map( 'absint', $order_ids ) );
// Execute and return connection.
return $resolver->get_connection();
}
/**
* Given an array of $args, this returns the connection config, merging the provided args
* with the defaults.
@@ -233,8 +136,8 @@ class Orders {
*/
if ( $not_manager ) {
return 'shop_order_refund' === $post_object->name
? self::get_customer_refund_connection( $resolver, \WC()->customer )
: self::get_customer_order_connection( $resolver, \WC()->customer );
? self::get_customer_refund_connection( $resolver, new Customer( 'session', ! is_user_logged_in() ) )
: self::get_customer_order_connection( $resolver, new Customer( 'session', ! is_user_logged_in() ) );
}
return $resolver->get_connection();
@@ -330,4 +233,102 @@ class Orders {
]
);
}
/**
* Returns order connection filter by customer.
*
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WPGraphQL\WooCommerce\Model\Customer $customer Customer object of querying user.
*
* @return array|\GraphQL\Deferred
*/
private static function get_customer_order_connection( $resolver, Customer $customer ) {
// If not "billing email" or "ID" set bail early by returning an empty connection.
if ( empty( $customer->billing['email'] ) && ( empty( absint( $customer->ID ) ) ) ) {
return [
'nodes' => [],
'edges' => [],
];
}
$target_customer_id = absint( $customer->ID );
$current_customer_id = absint( \WC()->customer->get_id() );
if ( ! empty( $target_customer_id ) ) {
$resolver->set_query_arg( 'customer_id', $target_customer_id );
$resolver->set_should_execute( $current_customer_id === $target_customer_id );
} elseif ( ! empty( $customer->billing['email'] ) ) {
$resolver->set_query_arg( 'billing_email', $customer->billing['email'] );
$resolver->set_should_execute( \WC()->customer->get_billing_email() === $customer->billing['email'] );
}
return $resolver->get_connection();
}
/**
* Returns refund connection filter by customer.
*
* @param \WPGraphQL\WooCommerce\Data\Connection\Order_Connection_Resolver $resolver Connection resolver.
* @param \WPGraphQL\WooCommerce\Model\Customer $customer Customer object of querying user.
*
* @return array|\GraphQL\Deferred
*/
private static function get_customer_refund_connection( Order_Connection_Resolver $resolver, Customer $customer ) {
$empty_results = [
'pageInfo' => null,
'nodes' => [],
'edges' => [],
];
// If not "billing email" or "ID" set bail early by returning an empty connection.
if ( empty( $customer->billing['email'] ) && empty( $customer->ID ) ) {
return $empty_results;
}
$order_ids = [];
$customer_id = $customer->ID;
$billing_email = $customer->billing['email'];
if ( ! empty( $customer_id ) ) {
$args = [
'customer_id' => $customer_id,
'return' => 'ids',
];
/** @var array<int> $order_ids_by_customer_id */
$order_ids_by_customer_id = wc_get_orders( $args );
if ( is_array( $order_ids_by_customer_id ) ) {
$order_ids = $order_ids_by_customer_id;
}
}
if ( ! empty( $billing_email ) ) {
$args = [
'billing_email' => $billing_email,
'return' => 'ids',
];
/** @var array<int> $order_ids_by_email */
$order_ids_by_email = wc_get_orders( $args );
// Merge the arrays of order IDs.
if ( is_array( $order_ids_by_email ) ) {
$order_ids = array_merge( $order_ids, $order_ids_by_email );
}
}
// If no orders found, return empty connection.
if ( empty( $order_ids ) ) {
return $empty_results;
}
// Remove duplicates.
$order_ids = array_unique( $order_ids );
// Set connection args.
$resolver->set_should_execute(
( 0 !== $customer_id && \WC()->customer->get_id() === $customer_id )
|| \WC()->customer->get_billing_email() === $billing_email
);
$resolver->set_query_arg( 'post_parent__in', array_map( 'absint', $order_ids ) );
// Execute and return connection.
return $resolver->get_connection();
}
}
+1 -3
View File
@@ -17,8 +17,6 @@ use WPGraphQL\Model\Model;
/**
* Class Customer
*
* @property \WC_Customer $wc_data
*
* @property int $ID
* @property string $id
* @property int $databaseId
@@ -86,7 +84,7 @@ class Customer extends Model {
return ( ! empty( $this->data->get_id() ) ) ? $this->data->get_id() : \WC()->session->get_customer_id();
},
'id' => function () {
return ( ! empty( $this->data->get_id() ) )
return ! empty( $this->data->get_id() )
? Relay::toGlobalId( 'user', $this->data->get_id() )
: 'guest';
},
@@ -62,7 +62,7 @@ class Payment_Method_Set_Default {
},
],
'token' => [
'type' => 'PaymentToken',
'type' => 'PaymentTokenInterface',
'description' => __( 'Preferred payment method token', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $payload ) {
return ! empty( $payload['token'] ) ? $payload['token'] : null;
@@ -1,6 +1,6 @@
<?php
/**
* WPInterface Type - Payment_Token
* WPInterface Type - Payment_Token_Interface
*
* @package WPGraphQL\WooCommerce\Type\WPInterface
* @since 0.10.1
@@ -8,13 +8,10 @@
namespace WPGraphQL\WooCommerce\Type\WPInterface;
use GraphQL\Error\UserError;
use GraphQLRelay\Relay;
/**
* Class Payment_Token
* Class Payment_Token_Interface
*/
class Payment_Token {
class Payment_Token_Interface {
/**
* Registers the "PaymentToken" interface.
*
@@ -22,7 +19,7 @@ class Payment_Token {
*/
public static function register_interface() {
register_graphql_interface_type(
'PaymentToken',
'PaymentTokenInterface',
[
'description' => __( 'Payment token object', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'Node' ],
@@ -36,13 +33,7 @@ class Payment_Token {
case 'eCheck':
return $type_registry->get_type( 'PaymentTokenECheck' );
default:
throw new UserError(
sprintf(
/* translators: %s: Payment token type */
__( 'The "%s" token type is not supported by the core WPGraphQL for WooCommerce (WooGraphQL) schema.', 'wp-graphql-woocommerce' ),
$type
)
);
return $type_registry->get_type( 'PaymentToken' );
}
},
]
@@ -58,20 +49,6 @@ class Payment_Token {
public static function get_fields( $other_fields = [] ) {
return array_merge(
[
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'Token ID unique identifier', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
return ! empty( $source->get_id() ) ? Relay::toGlobalId( 'token', $source->get_id() ) : null;
},
],
'tokenId' => [
'type' => [ 'non_null' => 'Integer' ],
'description' => __( 'Token database ID.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
return ! empty( $source->get_id() ) ? $source->get_id() : null;
},
],
'type' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Token type', 'wp-graphql-woocommerce' ),
@@ -12,7 +12,6 @@ use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\WooCommerce\Data\Connection\Product_Connection_Resolver;
use WPGraphQL\WooCommerce\Data\Connection\Variation_Attribute_Connection_Resolver;
use WPGraphQL\WooCommerce\Type\WPObject\Meta_Data_Type;
/**
@@ -78,208 +77,14 @@ class Product_Variation {
*/
public static function get_fields() {
return [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'Product or variation global ID', 'wp-graphql-woocommerce' ),
],
'databaseId' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'Product or variation ID', 'wp-graphql-woocommerce' ),
],
'name' => [
'type' => 'String',
'description' => __( 'Product name', 'wp-graphql-woocommerce' ),
],
'date' => [
'type' => 'String',
'description' => __( 'Date variation created', 'wp-graphql-woocommerce' ),
],
'modified' => [
'type' => 'String',
'description' => __( 'Date variation last updated', 'wp-graphql-woocommerce' ),
],
'description' => [
'type' => 'String',
'description' => __( 'Product description', 'wp-graphql-woocommerce' ),
],
'sku' => [
'type' => 'String',
'description' => __( 'Product variation SKU (Stock-keeping unit)', 'wp-graphql-woocommerce' ),
],
'price' => [
'type' => 'String',
'description' => __( 'Product variation\'s active price', 'wp-graphql-woocommerce' ),
'args' => [
'format' => [
'type' => 'PricingFieldFormatEnum',
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
],
],
'resolve' => static function ( $source, $args ) {
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
// @codingStandardsIgnoreLine.
return $source->priceRaw;
} else {
return $source->price;
}
},
],
'regularPrice' => [
'type' => 'String',
'description' => __( 'Product variation\'s regular price', 'wp-graphql-woocommerce' ),
'args' => [
'format' => [
'type' => 'PricingFieldFormatEnum',
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
],
],
'resolve' => static function ( $source, $args ) {
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
// @codingStandardsIgnoreLine.
return $source->regularPriceRaw;
} else {
// @codingStandardsIgnoreLine.
return $source->regularPrice;
}
},
],
'salePrice' => [
'type' => 'String',
'description' => __( 'Product variation\'s sale price', 'wp-graphql-woocommerce' ),
'args' => [
'format' => [
'type' => 'PricingFieldFormatEnum',
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
],
],
'resolve' => static function ( $source, $args ) {
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
// @codingStandardsIgnoreLine.
return $source->salePriceRaw;
} else {
// @codingStandardsIgnoreLine.
return $source->salePrice;
}
},
],
'dateOnSaleFrom' => [
'type' => 'String',
'description' => __( 'Date on sale from', 'wp-graphql-woocommerce' ),
],
'dateOnSaleTo' => [
'type' => 'String',
'description' => __( 'Date on sale to', 'wp-graphql-woocommerce' ),
],
'onSale' => [
'type' => 'Boolean',
'description' => __( 'Is variation on sale?', 'wp-graphql-woocommerce' ),
],
'status' => [
'type' => 'String',
'description' => __( 'Variation status', 'wp-graphql-woocommerce' ),
],
'purchasable' => [
'type' => 'Boolean',
'description' => __( 'If product variation can be bought', 'wp-graphql-woocommerce' ),
],
'virtual' => [
'type' => 'Boolean',
'description' => __( 'Is product virtual?', 'wp-graphql-woocommerce' ),
],
'downloadable' => [
'type' => 'Boolean',
'description' => __( 'Is downloadable?', 'wp-graphql-woocommerce' ),
],
'downloads' => [
'type' => [ 'list_of' => 'ProductDownload' ],
'description' => __( 'Product downloads', 'wp-graphql-woocommerce' ),
],
'downloadLimit' => [
'type' => 'Int',
'description' => __( 'Download limit', 'wp-graphql-woocommerce' ),
],
'downloadExpiry' => [
'type' => 'Int',
'description' => __( 'Download expiry', 'wp-graphql-woocommerce' ),
],
'taxStatus' => [
'type' => 'TaxStatusEnum',
'description' => __( 'Tax status', 'wp-graphql-woocommerce' ),
],
'taxClass' => [
'type' => 'TaxClassEnum',
'description' => __( 'Product variation tax class', 'wp-graphql-woocommerce' ),
],
'manageStock' => [
'type' => 'ManageStockEnum',
'description' => __( 'if/how product variation stock is managed', 'wp-graphql-woocommerce' ),
],
'stockQuantity' => [
'type' => 'Int',
'description' => __( 'Product variation stock quantity', 'wp-graphql-woocommerce' ),
],
'stockStatus' => [
'type' => 'StockStatusEnum',
'description' => __( 'Product stock status', 'wp-graphql-woocommerce' ),
],
'backorders' => [
'type' => 'BackordersEnum',
'description' => __( 'Product variation backorders', 'wp-graphql-woocommerce' ),
],
'backordersAllowed' => [
'type' => 'Boolean',
'description' => __( 'Can product be backordered?', 'wp-graphql-woocommerce' ),
],
'weight' => [
'type' => 'String',
'description' => __( 'Product variation weight', 'wp-graphql-woocommerce' ),
],
'length' => [
'type' => 'String',
'description' => __( 'Product variation length', 'wp-graphql-woocommerce' ),
],
'width' => [
'type' => 'String',
'description' => __( 'Product variation width', 'wp-graphql-woocommerce' ),
],
'height' => [
'type' => 'String',
'description' => __( 'Product variation height', 'wp-graphql-woocommerce' ),
],
'menuOrder' => [
'type' => 'Int',
'description' => __( 'Menu order', 'wp-graphql-woocommerce' ),
],
'purchaseNote' => [
'type' => 'String',
'description' => __( 'Product variation purchase_note', 'wp-graphql-woocommerce' ),
],
'shippingClass' => [
'shippingClass' => [
'type' => 'String',
'description' => __( 'Product variation shipping class', 'wp-graphql-woocommerce' ),
],
'catalogVisibility' => [
'type' => 'CatalogVisibilityEnum',
'description' => __( 'Product variation catalog visibility', 'wp-graphql-woocommerce' ),
],
'hasAttributes' => [
'hasAttributes' => [
'type' => 'Boolean',
'description' => __( 'Does product variation have any visible attributes', 'wp-graphql-woocommerce' ),
],
'type' => [
'type' => 'ProductTypesEnum',
'description' => __( 'Product type', 'wp-graphql-woocommerce' ),
],
'image' => [
'type' => 'MediaItem',
'description' => __( 'Product variation main image', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args, AppContext $context ) {
return ! empty( $source->image_id )
? $context->get_loader( 'post' )->load_deferred( $source->image_id )
: null;
},
],
'metaData' => Meta_Data_Type::get_metadata_field_definition(),
];
}
}
@@ -25,11 +25,8 @@ class Product {
* @return void
*/
public static function register_interface() {
// Register the fields to the Product Interface
// the product interface is defined by the post_type registration.
register_graphql_fields( 'Product', self::get_fields() );
register_graphql_field(
'RootQuery',
'product',
+122 -47
View File
@@ -10,6 +10,7 @@
namespace WPGraphQL\WooCommerce\Type\WPObject;
use GraphQL\Deferred;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
@@ -221,7 +222,7 @@ class Customer_Type {
'Customer',
[
'availablePaymentMethods' => [
'type' => [ 'list_of' => 'PaymentToken' ],
'type' => [ 'list_of' => 'PaymentTokenInterface' ],
'description' => __( 'Customer\'s stored payment tokens.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( get_current_user_id() === $source->ID ) {
@@ -285,56 +286,130 @@ class Customer_Type {
* @return void
*/
public static function register_session_handler_fields() {
/**
* Register the "sessionToken" field to the "Customer" type.
*/
register_graphql_field(
'Customer',
'sessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->ID || 'guest' === $source->id ) {
/**
* Session handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
$token_type = woographql_setting( 'set_session_token_type', 'legacy' );
if ( in_array( $token_type, [ 'legacy', 'both' ], true ) ) {
/**
* Register the "sessionToken" field to the "Customer" type.
*/
register_graphql_field(
'Customer',
'sessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->ID || 'guest' === $source->id ) {
return new Deferred(
static function () {
/**
* Session handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
}
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
}
);
}
return null;
},
]
);
/**
* Register the "wooSessionToken" field to the "User" type.
*/
register_graphql_field(
'User',
'wooSessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->userId ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
/**
* Session handler
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return null;
},
]
);
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
}
/**
* Register the "wooSessionToken" field to the "User" type.
*/
register_graphql_field(
'User',
'wooSessionToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->userId ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
return new Deferred(
static function () {
/**
* Session handler
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return null;
},
]
);
return apply_filters( 'graphql_customer_session_token', $session->build_token() );
}
);
}
return null;
},
]
);
}
if ( in_array( $token_type, [ 'store-api', 'both' ], true ) ) {
/**
* Register the "cartToken" field to the "Customer" type.
*/
register_graphql_field(
'Customer',
'cartToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->ID || 'guest' === $source->id ) {
return new Deferred(
static function () {
/**
* Session handler.
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return apply_filters( 'graphql_cart_token', $session->build_cart_token() );
}
);
}
return null;
},
]
);
/**
* Register the "cartToken" field to the "User" type.
*/
register_graphql_field(
'User',
'cartToken',
[
'type' => 'String',
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( \get_current_user_id() === $source->userId ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
return new Deferred(
static function () {
/**
* Session handler
*
* @var \WPGraphQL\WooCommerce\Utils\QL_Session_Handler $session
*/
$session = \WC()->session;
return apply_filters( 'graphql_cart_token', $session->build_cart_token() );
}
);
}
return null;
},
]
);
}
}
/**
@@ -12,6 +12,7 @@ namespace WPGraphQL\WooCommerce\Type\WPObject;
use WPGraphQL\AppContext;
use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
use WPGraphQL\WooCommerce\Data\Connection\Product_Connection_Resolver;
use WPGraphQL\WooCommerce\Data\Factory;
/**
@@ -211,7 +212,7 @@ class Order_Item_Type {
'oneToOne' => true,
'resolve' => static function ( $source, array $args, AppContext $context, $info ) {
$id = $source->productId; // @phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
$resolver = new Product_Connection_Resolver( $source, $args, $context, $info );
return $resolver
->one_to_one()
@@ -10,7 +10,7 @@
namespace WPGraphQL\WooCommerce\Type\WPObject;
use WPGraphQL\WooCommerce\Type\WPInterface\Payment_Token;
use WPGraphQL\WooCommerce\Type\WPInterface\Payment_Token_Interface;
/**
* Class Payment_Token_Types
@@ -22,21 +22,29 @@ class Payment_Token_Types {
* @return void
*/
public static function register() {
register_graphql_object_type(
'PaymentToken',
[
'description' => __( 'A payment token', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'PaymentTokenInterface' ],
'fields' => [],
]
);
register_graphql_object_type(
'PaymentTokenCC',
[
'description' => __( 'A credit cart payment token', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'PaymentToken' ],
'fields' => Payment_Token::get_fields( self::get_credit_card_fields() ),
'description' => __( 'A credit card payment token', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'PaymentTokenInterface' ],
'fields' => Payment_Token_Interface::get_fields( self::get_credit_card_fields() ),
]
);
register_graphql_object_type(
'PaymentTokenECheck',
[
'description' => __( 'A electronic check payment token', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'PaymentToken' ],
'fields' => Payment_Token::get_fields( self::get_e_check_fields() ),
'description' => __( 'An electronic check payment token', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'PaymentTokenInterface' ],
'fields' => Payment_Token_Interface::get_fields( self::get_e_check_fields() ),
]
);
}
@@ -54,10 +54,10 @@ class Shipping_Rate_Type {
},
],
'cost' => [
'type' => 'String',
'type' => 'Float',
'description' => __( 'Shipping rate cost', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
return ! empty( $source->get_cost() ) ? $source->get_cost() : null;
return ! is_null( $source->get_cost() ) ? $source->get_cost() : null;
},
],
],
+31 -7
View File
@@ -118,6 +118,8 @@ class QL_Session_Handler extends WC_Session_Handler {
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'woographql_update_session', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'shutdown', [ $this, 'save_data' ] );
add_filter( 'graphql_jwt_auth_after_authenticate', [ $this, 'reinitialize_session_token' ], 10 );
add_filter( 'graphql_login_payload', [ $this, 'reinitialize_session_token' ], 10 );
} else {
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_cookie' ], 10 );
add_action( 'shutdown', [ $this, 'save_data' ], 20 );
@@ -185,11 +187,11 @@ class QL_Session_Handler extends WC_Session_Handler {
if ( $token->exp < $this->_session_expiration ) {
$this->update_session_timestamp( (string) $this->_customer_id, $this->_session_expiration );
}
} elseif ( is_wp_error( $token ) ) {
} elseif ( Router::is_graphql_http_request() && is_wp_error( $token ) ) {
add_filter(
'graphql_woocommerce_session_token_errors',
static function ( $errors ) use ( $token ) {
$errors = $token->get_error_message();
$errors = $token->get_error_code() . ': ' . $token->get_error_message();
return $errors;
}
);
@@ -214,6 +216,29 @@ class QL_Session_Handler extends WC_Session_Handler {
}
}
/**
* Reinitialize session token in response after authentication in GraphQL.
*
* @param array $response The authentication response.
*
* @return array
*/
public function reinitialize_session_token( $response ) {
$this->init_session_token();
$token = $this->build_token();
if ( $token ) {
$response['session_token'] = $token;
}
// Add Store API Cart-Token if enabled.
$cart_token = $this->build_cart_token();
if ( ! empty( $cart_token ) ) {
$response['cart_token'] = $cart_token;
}
return $response;
}
/**
* Retrieve and decrypt the session data from session, if set. Otherwise return false.
*
@@ -480,7 +505,7 @@ class QL_Session_Handler extends WC_Session_Handler {
*
* @return string|null Cart-Token JWT or null if feature disabled or unavailable.
*/
protected function build_cart_token() {
public function build_cart_token() {
// Check if Store API token generation is enabled.
$token_type = woographql_setting( 'set_session_token_type', 'legacy' );
if ( ! in_array( $token_type, [ 'store-api', 'both' ], true ) ) {
@@ -633,10 +658,9 @@ class QL_Session_Handler extends WC_Session_Handler {
*/
public function set_session_expiration() {
$this->_session_issued = time();
// 47 hours.
$this->_session_expiring = apply_filters( 'wc_session_expiring', $this->_session_issued + ( 60 * 60 * 47 ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
// 48 hours.
$this->_session_expiration = apply_filters( 'wc_session_expiration', $this->_session_issued + ( 60 * 60 * 48 ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
parent::set_session_expiration();
$this->_session_expiration = apply_filters_deprecated(
'graphql_woocommerce_cart_session_expire',
[ $this->_session_expiration ],
+8
View File
@@ -939,6 +939,14 @@ class GraphQLE2E extends \Codeception\Module {
$zone->add_shipping_method( 'flat_rate' );
$zone->add_shipping_method( 'free_shipping' );
$zone = new \WC_Shipping_Zone();
$zone->set_zone_name( 'US' );
$zone->set_zone_order( 5 );
$zone->add_location( 'US:NY', 'state' );
$zone->save();
$zone->add_shipping_method( 'flat_rate' );
$zone->add_shipping_method( 'free_shipping' );
global $wp_rewrite;
//Write the rule
@@ -319,7 +319,7 @@ $authToken = $success['data']['login']['authToken'];
// any existing sessions.
$new_session_token = $success['data']['login']['sessionToken'];
$I->wantTo( 'Check the cart and should be empty' );
$I->wantTo( 'Check the cart and should contain the contents from our guest session' );
$cart_query = '
query {
@@ -346,14 +346,5 @@ $response = $I->sendGraphQLRequest(
'woocommerce-session' => "Session {$new_session_token}",
]
);
$expected_results = [
'data' => [
'cart' => [
'contents' => [
'nodes' => [],
],
],
],
];
$I->assertEquals( $expected_results, $response );
+3 -2
View File
@@ -488,6 +488,7 @@ class QLSessionHandlerCest {
$actual,
[
$I->expectField( 'cart.contents.nodes.#.key', $cart_item_key ),
$I->expectField( 'cart.availableShippingMethods', Signal::NOT_FALSY ),
$I->expectNode(
'cart.availableShippingMethods',
[
@@ -496,14 +497,14 @@ class QLSessionHandlerCest {
$I->expectNode(
'rates',
[
$I->expectField( 'cost', '0.00' ),
$I->expectField( 'cost', 0 ),
$I->expectField( 'label', 'Flat rate' ),
]
),
$I->expectNode(
'rates',
[
$I->expectField( 'cost', '0.00' ),
$I->expectField( 'cost', 0 ),
$I->expectField( 'label', 'Free shipping' ),
]
),
-18
View File
@@ -496,8 +496,6 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
customer(id: $id) {
id
availablePaymentMethods {
id
tokenId
... on PaymentTokenCC {
last4
expiryMonth
@@ -509,16 +507,12 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
}
}
availablePaymentMethodsCC {
id
tokenId
last4
expiryMonth
expiryYear
cardType
}
availablePaymentMethodsEC {
id
tokenId
last4
}
}
@@ -554,8 +548,6 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
$this->expectedNode(
'customer.availablePaymentMethods',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_cc->get_id() ) ),
$this->expectedField( 'tokenId', $token_cc->get_id() ),
$this->expectedField( 'last4', 1234 ),
$this->expectedField( 'expiryMonth', $expiry_month ),
$this->expectedField( 'expiryYear', $expiry_year ),
@@ -565,8 +557,6 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
$this->expectedNode(
'customer.availablePaymentMethodsCC',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_cc->get_id() ) ),
$this->expectedField( 'tokenId', $token_cc->get_id() ),
$this->expectedField( 'last4', 1234 ),
$this->expectedField( 'expiryMonth', $expiry_month ),
$this->expectedField( 'expiryYear', $expiry_year ),
@@ -576,8 +566,6 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
$this->expectedNode(
'customer.availablePaymentMethodsEC',
[
$this->not()->expectedField( 'id', $this->toRelayId( 'token', $token_cc->get_id() ) ),
$this->not()->expectedField( 'tokenId', $token_cc->get_id() ),
$this->not()->expectedField( 'last4', 1234 ),
$this->not()->expectedField( 'expiryMonth', $expiry_month ),
$this->not()->expectedField( 'expiryYear', $expiry_year ),
@@ -587,24 +575,18 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
$this->expectedNode(
'customer.availablePaymentMethods',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_ec->get_id() ) ),
$this->expectedField( 'tokenId', $token_ec->get_id() ),
$this->expectedField( 'last4', 4567 ),
]
),
$this->expectedNode(
'customer.availablePaymentMethodsCC',
[
$this->not()->expectedField( 'id', $this->toRelayId( 'token', $token_ec->get_id() ) ),
$this->not()->expectedField( 'tokenId', $token_ec->get_id() ),
$this->not()->expectedField( 'last4', 4567 ),
]
),
$this->expectedNode(
'customer.availablePaymentMethodsEC',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_ec->get_id() ) ),
$this->expectedField( 'tokenId', $token_ec->get_id() ),
$this->expectedField( 'last4', 4567 ),
]
),
+9 -3
View File
@@ -33,8 +33,14 @@ class PaymentMethodMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\W
setDefaultPaymentMethod(input: { tokenId: $tokenId }) {
status
token {
id
type
isDefault
... on PaymentTokenCC {
last4
}
... on PaymentTokenECheck {
last4
}
}
}
}
@@ -67,7 +73,7 @@ class PaymentMethodMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\W
$this->expectedObject(
'setDefaultPaymentMethod.token',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_ec->get_id() ) ),
$this->expectedField( 'type', 'eCheck' ),
$this->expectedField( 'isDefault', true ),
]
),
@@ -83,7 +89,7 @@ class PaymentMethodMutationsTest extends \Tests\WPGraphQL\WooCommerce\TestCase\W
$this->expectedObject(
'setDefaultPaymentMethod.token',
[
$this->expectedField( 'id', $this->toRelayId( 'token', $token_cc->get_id() ) ),
$this->expectedField( 'type', 'CC' ),
$this->expectedField( 'isDefault', true ),
]
),
+22
View File
@@ -163,6 +163,28 @@ function init_auth_router() {
}
add_action( 'plugins_loaded', 'WPGraphQL\WooCommerce\init_auth_router' );
/**
* Prevent WooCommerce from loading cart during 'init' for GraphQL requests.
*
* WooCommerce calls wc_load_cart() during the 'init' action for frontend requests.
* This happens before JWT authentication can set the current user, causing the
* session to be initialized with the wrong user context.
*
* By making WooCommerce think GraphQL requests are REST API requests, we prevent
* the early cart loading. The cart is then loaded later during 'graphql_before_execute'
* after JWT authentication has had a chance to run.
*
* @return void
*/
function prevent_early_wc_cart_loading() {
if ( ! is_graphql_http_request() ) {
return;
}
add_filter( 'woocommerce_is_rest_api_request', '__return_true' );
}
add_action( 'plugins_loaded', 'WPGraphQL\WooCommerce\prevent_early_wc_cart_loading', 0 );
// Load constants.
constants();