Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Connections need to connect to Types that implement the Node interface #675

Merged
merged 15 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified bin/_env.sh
100644 → 100755
Empty file.
Empty file modified bin/_lib.sh
100644 → 100755
Empty file.
Empty file modified bin/codecept
100644 → 100755
Empty file.
Empty file modified bin/entrypoint.sh
100644 → 100755
Empty file.
Empty file modified bin/install-test-env.local.sh
100644 → 100755
Empty file.
Empty file modified bin/remove-testing-library.local.sh
100644 → 100755
Empty file.
Empty file modified bin/remove-wordpress.local.sh
100644 → 100755
Empty file.
25 changes: 21 additions & 4 deletions includes/class-core-schema-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace WPGraphQL\WooCommerce;

use GraphQL\Error\UserError;
use WPGraphQL\WooCommerce\Data\Loader\WC_Customer_Loader;
use WPGraphQL\WooCommerce\Data\Loader\WC_CPT_Loader;
use WPGraphQL\WooCommerce\Data\Loader\WC_Db_Loader;
Expand Down Expand Up @@ -134,10 +135,26 @@ public static function add_filters() {
*/
public static function register_post_types( $args, $post_type ) {
if ( 'product' === $post_type ) {
$args['show_in_graphql'] = true;
$args['graphql_single_name'] = 'Product';
$args['graphql_plural_name'] = 'Products';
$args['skip_graphql_type_registry'] = true;
$args['show_in_graphql'] = true;
$args['graphql_single_name'] = 'Product';
$args['graphql_plural_name'] = 'Products';
$args['graphql_kind'] = 'interface';
$args['graphql_register_root_field'] = false;
$args['graphql_register_root_connection'] = false;
$args['graphql_resolve_type'] = static function( $value ) {
$type_registry = \WPGraphQL::get_type_registry();
$possible_types = WP_GraphQL_WooCommerce::get_enabled_product_types();
if ( isset( $possible_types[ $value->type ] ) ) {
return $type_registry->get_type( $possible_types[ $value->type ] );
}
throw new UserError(
sprintf(
/* translators: %s: Product type */
__( 'The "%s" product type is not supported by the core WPGraphQL WooCommerce (WooGraphQL) schema.', 'wp-graphql-woocommerce' ),
$value->type
)
);
};
}
if ( 'product_variation' === $post_type ) {
$args['show_in_graphql'] = true;
Expand Down
2 changes: 1 addition & 1 deletion includes/class-type-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
Type\WPInputObject\Orderby_Inputs::register();

// Interfaces.
Type\WPInterface\Product::register_interface( $type_registry );
Type\WPInterface\Product::register_interface();
Type\WPInterface\Attribute::register_interface( $type_registry );
Type\WPInterface\Product_Attribute::register_interface( $type_registry );
Type\WPInterface\Cart_Error::register_interface( $type_registry );
Expand Down
5 changes: 4 additions & 1 deletion includes/connection/class-coupons.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public static function get_connection_config( $args = [] ): array {
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'shop_coupon' );

if ( ! self::should_execute() ) {
return [];
return [
'edges' => [],
'nodes' => [],
];
}

return $resolver->get_connection();
Expand Down
5 changes: 4 additions & 1 deletion includes/connection/class-customers.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ public static function register_connections() {
$resolver->set_query_arg( 'role', 'customer' );

if ( ! self::should_execute() ) {
return [];
return [
'nodes' => [],
'edges' => [],
];
}

return $resolver->get_connection();
Expand Down
124 changes: 80 additions & 44 deletions includes/connection/class-products.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public static function register_connections() {
[
'fromType' => 'Coupon',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post__in', $source->product_ids );

Expand All @@ -52,7 +54,9 @@ public static function register_connections() {
'fromType' => 'Coupon',
'fromFieldName' => 'excludedProducts',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post__in', $source->excluded_product_ids );

Expand Down Expand Up @@ -83,7 +87,9 @@ public static function register_connections() {
]
),
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

// Bypass randomization by default for pagination support.
if ( empty( $args['where']['shuffle'] ) ) {
Expand Down Expand Up @@ -114,7 +120,9 @@ function() {
'fromType' => 'Product',
'fromFieldName' => 'upsell',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post__in', $source->upsell_ids );

Expand All @@ -135,7 +143,9 @@ function() {
[
'fromType' => 'GroupProduct',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post__in', $source->grouped_ids );

Expand All @@ -154,7 +164,9 @@ function() {
$cross_sell_config = [
'fromFieldName' => 'crossSell',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post__in', $source->cross_sell_ids );

Expand Down Expand Up @@ -185,7 +197,9 @@ function() {
'toType' => 'ProductVariation',
'fromFieldName' => 'variations',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'post_parent', $source->ID );
$resolver->set_query_arg( 'post_type', 'product_variation' );
Expand Down Expand Up @@ -216,7 +230,10 @@ function() {
return null;
}

add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver->set_query_arg( 'p', $source->parent_id );

$resolver = self::set_ordering_query_args( $resolver, $args );
Expand All @@ -228,14 +245,16 @@ function() {

// Taxonomy To Product resolver.
$resolve_product_from_taxonomy = function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$tax_query = [
[
// WPCS: slow query ok.
'taxonomy' => $source->taxonomyName, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
'field' => 'term_id',
'terms' => $source->term_id,
'taxonomy' => $source->taxonomyName, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
'field' => 'term_id',
'terms' => $source->term_id,
],
];
$resolver->set_query_arg( 'tax_query', $tax_query );
Expand All @@ -245,37 +264,37 @@ function() {
return $resolver->get_connection();
};

// From ProductCategory.
register_graphql_connection(
self::get_connection_config(
[
'fromType' => 'ProductCategory',
'resolve' => $resolve_product_from_taxonomy,
]
)
);

// From ProductTag.
register_graphql_connection(
self::get_connection_config(
[
'fromType' => 'ProductTag',
'resolve' => $resolve_product_from_taxonomy,
]
)
);
// // From ProductCategory.
// register_graphql_connection(
// self::get_connection_config(
// [
// 'fromType' => 'ProductCategory',
// 'resolve' => $resolve_product_from_taxonomy,
// ]
// )
// );

// // From ProductTag.
// register_graphql_connection(
// self::get_connection_config(
// [
// 'fromType' => 'ProductTag',
// 'resolve' => $resolve_product_from_taxonomy,
// ]
// )
// );

// From WooCommerce product attributes.
$attributes = WP_GraphQL_WooCommerce::get_product_attribute_taxonomies();
foreach ( $attributes as $attribute ) {
register_graphql_connection(
self::get_connection_config(
[
'fromType' => ucfirst( graphql_format_field_name( $attribute ) ),
'resolve' => $resolve_product_from_taxonomy,
]
)
);
// register_graphql_connection(
// self::get_connection_config(
// [
// 'fromType' => ucfirst( graphql_format_field_name( $attribute ) ),
// 'resolve' => $resolve_product_from_taxonomy,
// ]
// )
// );
register_graphql_connection(
self::get_connection_config(
[
Expand Down Expand Up @@ -329,7 +348,9 @@ public static function get_connection_config( $args = [] ): array {
'queryClass' => '\WC_Product_Query',
'connectionArgs' => self::get_connection_args(),
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
add_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'product' );
remove_filter( 'graphql_post_object_connection_args', [ __CLASS__, 'bypass_get_args_sanitization' ], 10, 3 );

$resolver = self::set_ordering_query_args( $resolver, $args );

Expand All @@ -340,6 +361,19 @@ public static function get_connection_config( $args = [] ): array {
);
}

/**
* Bypass arg sanization in Post Object Connection Resolver.
*
* @param array $args Sanitized GraphQL args passed to the resolver.
* @param PostObjectConnectionResolver $connection_resolver Instance of the ConnectionResolver
* @param array $all_args array of arguments input in the field as part of the GraphQL query

* @return array
*/
public static function bypass_get_args_sanitization( $args, $connection_resolver, $all_args ) {
return $all_args;
}

/**
* Undocumented function
*
Expand Down Expand Up @@ -526,21 +560,22 @@ public static function get_connection_args(): array {
* 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 $query_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 $args All of the arguments for the query (not just the "where" args).
* @param AppContext $context The AppContext object.
* @param ResolveInfo $info The ResolveInfo object.
* @param mixed|string|array $post_type The post type for the query.
* @param array $query_args The mapped query arguments
* @param array $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 AppContext $context The AppContext object
* @param ResolveInfo $info The ResolveInfo object
* @param mixed|string|array $post_type The post type for the query
*
* @return array Query arguments.
*/
public static function map_input_fields_to_wp_query( $query_args, $where_args, $source, $args, $context, $info, $post_type ) {
public static function map_input_fields_to_wp_query( $query_args, $args, $source, $all_args, $context, $info, $post_type ) {
if ( ! in_array( 'product', $post_type, true ) && ! in_array( 'product_variation', $post_type, true ) ) {
return $query_args;
}

$where_args = $all_args['where'];
$query_args = array_merge(
$query_args,
map_shared_input_fields_to_wp_query( $where_args )
Expand Down Expand Up @@ -645,11 +680,11 @@ public static function map_input_fields_to_wp_query( $query_args, $where_args, $
}

if ( empty( $where_args['type'] ) && empty( $where_args['typeIn'] ) && ! empty( $where_args['supportedTypesOnly'] )
&& true === $where_args['supportedTypesOnly'] ) {
$supported_types = array_keys( WP_GraphQL_WooCommerce::get_enabled_product_types() );
$terms = ! empty( $where_args['typeNotIn'] )
? array_diff( $supported_types, $where_args['typeNotIn'] )
: $supported_types;
&& true === $where_args['supportedTypesOnly'] ) {
$supported_types = array_keys( WP_GraphQL_WooCommerce::get_enabled_product_types() );
$terms = ! empty( $where_args['typeNotIn'] )
? array_diff( $supported_types, $where_args['typeNotIn'] )
: $supported_types;
$tax_query[] = [
'taxonomy' => 'product_type',
'field' => 'slug',
Expand Down Expand Up @@ -787,7 +822,7 @@ public static function map_input_fields_to_wp_query( $query_args, $where_args, $
: PHP_INT_MAX;

$meta_query[] = apply_filters(
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
'woocommerce_get_min_max_price_meta_query',
[
'key' => '_price',
Expand Down Expand Up @@ -850,4 +885,5 @@ public static function map_input_fields_to_wp_query( $query_args, $where_args, $

return $query_args;
}

}
4 changes: 4 additions & 0 deletions includes/connection/class-wc-terms.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public static function register_connections() {
// Registers the connections between each allowed PostObjectType and it's TermObjects.
if ( ! empty( $wc_post_types ) && is_array( $wc_post_types ) ) {
foreach ( $wc_post_types as $post_type ) {
if ( 'product' === $post_type ) {
continue;
}

if ( in_array( $post_type, $tax_object->object_type, true ) ) {
$post_type_object = get_post_type_object( $post_type );
register_graphql_connection(
Expand Down
1 change: 1 addition & 0 deletions includes/connection/wc-cpt-connection-args.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function get_wc_cpt_connection_args(): array {
* @return array
*/
function map_shared_input_fields_to_wp_query( array $input, $ordering_meta = [] ) {

$args = [];
if ( ! empty( $input['include'] ) ) {
$args['post__in'] = $input['include'];
Expand Down
1 change: 1 addition & 0 deletions includes/type/interface/class-attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static function register_interface( &$type_registry ) {
'Attribute',
[
'description' => __( 'Attribute object', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'Node' ],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces must be used to indent lines; tabs are not allowed

'fields' => [
'name' => [
'type' => 'String',
Expand Down
4 changes: 1 addition & 3 deletions includes/type/interface/class-product-attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static function register_interface( &$type_registry ) {
'ProductAttribute',
[
'description' => __( 'Product attribute object', 'wp-graphql-woocommerce' ),
'interfaces' => [ 'Node' ],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces must be used to indent lines; tabs are not allowed

'fields' => self::get_fields(),
'resolveType' => function( $value ) use ( &$type_registry ) {
if ( $value->is_taxonomy() ) {
Expand All @@ -45,9 +46,6 @@ public static function get_fields() {
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'Attribute Global ID', 'wp-graphql-woocommerce' ),
'resolve' => function ( $attribute ) {
return ! empty( $attribute->_relay_id ) ? $attribute->_relay_id : null;
},
],
'attributeId' => [
'type' => [ 'non_null' => 'Int' ],
Expand Down
Loading