Files
wp-graphql-woocommerce/includes/connection/class-orders.php
T
Geoffrey K TaylorandGitHub 6dbdcb5ae8 fix: Product Attribute naming conventions changed (#603)
* fix: Product Attribute naming conventions changed

* devops: dev dependencies updated

* devops: dev dependencies updated

* devops: dev dependencies updated

* devops: Dockerfile updated

* devops: dev dependencies updated

* devops: dev dependencies updated

* devops: dev dependencies updated

* devops: Dockerfile updated

* chore: Docker composer configurations updated

* chore: Coding standards met.
2022-02-04 15:00:44 -05:00

462 lines
15 KiB
PHP

<?php
/**
* Connection - Orders
*
* Registers connections to Order
*
* @package WPGraphQL\WooCommerce\Connection
*/
namespace WPGraphQL\WooCommerce\Connection;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
/**
* Class - Orders
*/
class Orders {
/**
* Registers the various connections from other Types to Customer
*/
public static function register_connections() {
// From RootQuery To Orders.
register_graphql_connection( self::get_connection_config() );
// From Customer To Orders.
register_graphql_connection(
self::get_connection_config(
array(
'fromType' => 'Customer',
'fromFieldName' => 'orders',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'shop_order' );
return self::get_customer_order_connection( $resolver, $source );
},
)
)
);
// From RootQuery To Refunds.
register_graphql_connection(
self::get_connection_config(
array(
'toType' => 'Refund',
'fromFieldName' => 'refunds',
'connectionArgs' => self::get_refund_connection_args(),
),
'shop_order_refund'
)
);
// From Order To Refunds.
register_graphql_connection(
self::get_connection_config(
array(
'fromType' => 'Order',
'toType' => 'Refund',
'fromFieldName' => 'refunds',
'connectionArgs' => self::get_refund_connection_args(),
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'shop_order_refund' );
$resolver->set_query_arg( 'post_parent', $source->ID );
return $resolver->get_connection();
},
),
'shop_order_refund'
)
);
// From Customer To Refunds.
register_graphql_connection(
self::get_connection_config(
array(
'fromType' => 'Customer',
'toType' => 'Refund',
'fromFieldName' => 'refunds',
'connectionArgs' => self::get_refund_connection_args(),
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) {
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, 'shop_order_refund' );
$customer_orders = \wc_get_orders(
array(
'customer_id' => $source->ID,
'no_rows_found' => true,
'return' => 'ids',
)
);
$resolver->set_query_arg( 'post_parent__in', array_map( 'absint', $customer_orders ) );
return $resolver->get_connection();
},
),
'shop_order_refund'
)
);
}
/**
* Returns order connection filter by customer.
*
* @param PostObjectConnectionResolver $resolver Connection resolver.
* @param \WC_Customer|\WPGraphQL\WooCommerce\Model\Customer $customer Customer object of querying user.
*
* @return array
*/
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 array();
}
// If the querying user has a "billing email" set filter orders by user's billing email, otherwise filter by user's ID.
$meta_key = ! empty( $customer->get_billing_email() ) ? '_billing_email' : '_customer_user';
$meta_value = ! empty( $customer->get_billing_email() )
? $customer->get_billing_email()
: $customer->get_id();
$resolver->set_query_arg( 'meta_key', $meta_key );
$resolver->set_query_arg( 'meta_value', $meta_value );
return $resolver->get_connection();
}
/**
* Returns refund connection filter by customer.
*
* @param PostObjectConnectionResolver $resolver Connection resolver.
* @param \WC_Customer|\WPGraphQL\WooCommerce\Model\Customer $customer Customer object of querying user.
*
* @return array
*/
private static function get_customer_refund_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 array(
'pageInfo' => null,
'nodes' => array(),
'edges' => array(),
);
}
}
/**
* Given an array of $args, this returns the connection config, merging the provided args
* with the defaults.
*
* @param array $args Connection configuration.
* @param string $post_type Connection resolving post-type.
*
* @return array
*/
public static function get_connection_config( $args = array(), $post_type = 'shop_order' ): array {
// Get Post type object for use in connection resolve function.
$post_object = get_post_type_object( $post_type );
return array_merge(
array(
'fromType' => 'RootQuery',
'toType' => 'Order',
'fromFieldName' => 'orders',
'connectionArgs' => self::get_connection_args( 'private' ),
'queryClass' => '\WC_Order_Query',
'resolve' => function( $source, array $args, AppContext $context, ResolveInfo $info ) use ( $post_object ) {
// Check if user shop manager.
$not_manager = ! current_user_can( $post_object->cap->edit_posts );
// Remove any arguments that require querying user to have "shop manager" role.
$args = $not_manager && 'shop_order' === $post_object->name
? \array_intersect_key( $args, array_keys( self::get_connection_args( 'public' ) ) )
: $args;
// Initialize connection resolver.
$resolver = new PostObjectConnectionResolver( $source, $args, $context, $info, $post_object->name );
/**
* If not shop manager, restrict results to orders/refunds owned by querying user
* and return the connection.
*/
if ( 'shop_order_refund' === $post_object->name ) {
$empty_results = array(
'pageInfo' => null,
'nodes' => array(),
'edges' => array(),
);
return $not_manager
? $empty_results
: $resolver->get_connection();
}
return $not_manager
? self::get_customer_order_connection( $resolver, \WC()->customer )
: $resolver->get_connection();
},
),
$args
);
}
/**
* Returns array of where args.
*
* @param string $access Connection argument access-level.
* @return array
*/
public static function get_connection_args( $access = 'public' ): array {
switch ( $access ) {
case 'private':
return array_merge(
get_wc_cpt_connection_args(),
array(
'statuses' => array(
'type' => array( 'list_of' => 'OrderStatusEnum' ),
'description' => __( 'Limit result set to orders assigned a specific status.', 'wp-graphql-woocommerce' ),
),
'customerId' => array(
'type' => 'Int',
'description' => __( 'Limit result set to orders assigned a specific customer.', 'wp-graphql-woocommerce' ),
),
'customersIn' => array(
'type' => array( 'list_of' => 'Int' ),
'description' => __( 'Limit result set to orders assigned a specific group of customers.', 'wp-graphql-woocommerce' ),
),
'productId' => array(
'type' => 'Int',
'description' => __( 'Limit result set to orders assigned a specific product.', 'wp-graphql-woocommerce' ),
),
'orderby' => array(
'type' => array( 'list_of' => 'OrdersOrderbyInput' ),
'description' => __( 'What paramater to use to order the objects by.', 'wp-graphql-woocommerce' ),
),
)
);
case 'public':
default:
return array(
'statuses' => array(
'type' => array( 'list_of' => 'OrderStatusEnum' ),
'description' => __( 'Limit result set to orders assigned a specific status.', 'wp-graphql-woocommerce' ),
),
'productId' => array(
'type' => 'Int',
'description' => __( 'Limit result set to orders assigned a specific product.', 'wp-graphql-woocommerce' ),
),
'orderby' => array(
'type' => array( 'list_of' => 'OrdersOrderbyInput' ),
'description' => __( 'What paramater to use to order the objects by.', 'wp-graphql-woocommerce' ),
),
'search' => array(
'type' => 'String',
'description' => __( 'Limit results to those matching a string.', 'wp-graphql-woocommerce' ),
),
'dateQuery' => array(
'type' => 'DateQueryInput',
'description' => __( 'Filter the connection based on dates.', 'wp-graphql-woocommerce' ),
),
);
}//end switch
}
/**
* Returns array of where args.
*
* @return array
*/
public static function get_refund_connection_args(): array {
return array_merge(
get_wc_cpt_connection_args(),
array(
'statuses' => array(
'type' => array( 'list_of' => 'String' ),
'description' => __( 'Limit result set to refunds assigned a specific status.', 'wp-graphql-woocommerce' ),
),
'orderIn' => array(
'type' => array( 'list_of' => 'Int' ),
'description' => __( 'Limit result set to refunds from a specific group of order IDs.', 'wp-graphql-woocommerce' ),
),
)
);
}
/**
* 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 AppContext $context The AppContext passed down the GraphQL tree.
* @param ResolveInfo $info The ResolveInfo passed down the GraphQL tree.
*
* @return array
*/
public static function post_object_connection_query_args( $query_args, $source, $args, $context, $info ) {
$post_types = array( 'shop_order', 'shop_order_refund' );
$not_order_query = is_string( $query_args['post_type'] )
? empty( array_intersect( $post_types, array( $query_args['post_type'] ) ) )
: empty( array_intersect( $post_types, $query_args['post_type'] ) );
if ( $not_order_query ) {
return $query_args;
}
$query_args['type'] = \wc_get_order_types( 'view-orders' );
if ( empty( $args['where'] ) || empty( $args['where']['statuses'] ) ) {
$query_args['post_status'] = array_keys( \wc_get_order_statuses() );
}
return $query_args;
}
/**
* 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.
*
* @return array Query arguments.
*/
public static function map_input_fields_to_wp_query( $query_args, $where_args, $source, $args, $context, $info, $post_type ) {
$post_types = array( 'shop_order', 'shop_order_refund' );
if ( empty( array_intersect( $post_types, is_string( $post_type ) ? array( $post_type ) : $post_type ) ) ) {
return $query_args;
}
global $wpdb;
$query_args = array_merge(
$query_args,
map_shared_input_fields_to_wp_query( $where_args )
);
// Process order meta inputs.
$metas = array( 'customerId', 'customersIn' );
$meta_query = array();
foreach ( $metas as $field ) {
if ( isset( $query_args[ $field ] ) ) {
$value = $query_args[ $field ];
switch ( $field ) {
case 'customerId':
case 'customersIn':
if ( is_null( $value ) ) {
$meta_query[] = array(
'key' => '_customer_user',
'value' => 0,
'compare' => '=',
);
} else {
$meta_query[] = array(
'key' => '_customer_user',
'value' => $value,
'compare' => is_array( $value ) ? 'IN' : '=',
);
}
}
}
}//end foreach
if ( ! empty( $meta_query ) ) {
$query_args['meta_query'] = $meta_query;
// WPCS: slow query ok.
}
$key_mapping = array(
'statuses' => 'post_status',
'orderIn' => 'post_parent__in',
);
$prefixer = function( $status ) {
$statuses = array_keys( \wc_get_order_statuses() );
if ( in_array( "wc-{$status}", $statuses, true ) ) {
return "wc-{$status}";
}
return $status;
};
foreach ( $key_mapping as $key => $field ) {
if ( isset( $query_args[ $key ] ) ) {
$query_args[ $field ] = 'statuses' === $key
? array_map( $prefixer, $query_args[ $key ] )
: $query_args[ $key ];
unset( $query_args[ $key ] );
}
}
// @codingStandardsIgnoreStart
// if ( ! empty( $where_args['statuses'] ) ) {
// if ( 1 === count( $where_args ) ) {
// $query_args['status'] = $where_args['statuses'][0];
// } else {
// $query_args['status'] = $where_args['statuses'];
// }
// }
// @codingStandardsIgnoreEnd
// Search by product.
if ( ! empty( $where_args['productId'] ) ) {
$order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT order_id
FROM {$wpdb->prefix}woocommerce_order_items
WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
AND order_item_type = 'line_item'",
absint( $where_args['productId'] )
)
);
// Force WP_Query return empty if don't found any order.
$query_args['post__in'] = ! empty( $order_ids ) ? $order_ids : array( 0 );
}
// Search.
if ( ! empty( $query_args['s'] ) ) {
$order_ids = wc_order_search( $query_args['s'] );
if ( ! empty( $order_ids ) ) {
unset( $query_args['s'] );
$query_args['post__in'] = isset( $query_args['post__in'] )
? array_intersect( $order_ids, $query_args['post__in'] )
: $order_ids;
}
}
/**
* Filter the input fields
* This allows plugins/themes to hook in and alter what $args should be allowed to be passed
* from a GraphQL Query to the WP_Query
*
* @param array $args The mapped query arguments
* @param array $where_args Query "where" args
* @param mixed $source The query results for a query calling this
* @param array $all_args All of the arguments for the query (not just the "where" args)
* @param AppContext $context The AppContext object
* @param ResolveInfo $info The ResolveInfo object
* @param mixed|string|array $post_type The post type for the query
*/
$query_args = apply_filters(
'graphql_map_input_fields_to_order_query',
$query_args,
$where_args,
$source,
$args,
$context,
$info,
$post_type
);
return $query_args;
}
}