2019-03-31 00:21:31 -04:00
<? php
/**
* ConnectionResolver - Order_Connection_Resolver
*
* Resolves connections to Orders
*
2019-10-25 19:13:36 -04:00
* @package WPGraphQL\WooCommerce\Data\Connection
2019-03-31 00:21:31 -04:00
* @since 0.0.1
*/
2019-10-25 19:13:36 -04:00
namespace WPGraphQL\WooCommerce\Data\Connection ;
2019-03-31 00:21:31 -04:00
2023-06-13 23:17:02 +03:00
use GraphQL\Error\InvariantViolation ;
2019-03-31 00:21:31 -04:00
use WPGraphQL\Data\Connection\AbstractConnectionResolver ;
/**
* Class Order_Connection_Resolver
*/
class Order_Connection_Resolver extends AbstractConnectionResolver {
2020-01-13 14:24:11 -05:00
/**
2020-03-27 12:18:06 -04:00
* Include CPT Loader connection common functions.
2020-01-13 14:24:11 -05:00
*/
2020-03-27 12:18:06 -04:00
use WC_CPT_Loader_Common ;
2019-04-10 12:26:37 -04:00
/**
* The name of the post type, or array of post types the connection resolver is resolving for
*
2023-06-13 23:17:02 +03:00
* @var string
2019-04-10 12:26:37 -04:00
*/
protected $post_type ;
2023-06-21 13:21:29 -04:00
/**
* This stores the should
*
* @var boolean
*/
protected $should_execute = false ;
2019-04-10 12:26:37 -04:00
/**
* Refund_Connection_Resolver constructor.
*
2023-07-19 23:01:50 +03:00
* @param mixed $source The object passed down from the previous level in the Resolve tree.
* @param array $args The input arguments for the query.
* @param \WPGraphQL\AppContext $context The context of the request.
* @param \GraphQL\Type\Definition\ResolveInfo $info The resolve info passed down the Resolve tree.
* @param string $post_type The post type for the connection resolver.
2019-04-10 12:26:37 -04:00
*/
2023-06-21 13:21:29 -04:00
public function __construct ( $source , $args , $context , $info , $post_type = 'shop_order' ) {
2019-04-10 12:26:37 -04:00
/**
2023-06-21 13:21:29 -04:00
* Set the post type for the resolver.
2019-04-10 12:26:37 -04:00
*/
2023-06-21 13:21:29 -04:00
$this -> post_type = $post_type ;
2020-08-11 19:18:19 -04:00
2019-04-10 12:26:37 -04:00
/**
2023-06-21 13:21:29 -04:00
* Call the parent construct to setup class data.
2019-04-10 12:26:37 -04:00
*/
parent :: __construct ( $source , $args , $context , $info );
2023-06-21 13:21:29 -04:00
/**
* Default to true.
*/
$this -> should_execute = true ;
2019-04-10 12:26:37 -04:00
}
2020-03-20 22:59:47 -04:00
/**
* Return the name of the loader to be used with the connection resolver
*
* @return string
*/
public function get_loader_name () {
2021-06-03 21:37:09 -04:00
return 'wc_post' ;
2020-03-20 22:59:47 -04:00
}
/**
* Given an ID, return the model for the entity or null
*
2021-01-21 22:41:43 -05:00
* @param integer $id Node ID.
2020-03-20 22:59:47 -04:00
*
2023-07-19 23:01:50 +03:00
* @return mixed|\WPGraphQL\WooCommerce\Model\Order|null
2020-03-20 22:59:47 -04:00
*/
public function get_node_by_id ( $id ) {
2020-03-24 20:55:13 -04:00
return $this -> get_cpt_model_by_id ( $id );
2020-03-20 22:59:47 -04:00
}
2019-03-31 00:21:31 -04:00
/**
2020-01-13 13:08:10 -05:00
* Checks if user is authorized to query orders
2019-03-31 00:21:31 -04:00
*
* @return bool
*/
public function should_execute () {
2023-06-13 23:17:02 +03:00
/**
* Get order post type.
*
* @var \WP_Post_Type $post_type_obj
*/
2020-01-13 13:08:10 -05:00
$post_type_obj = get_post_type_object ( $this -> post_type );
if ( current_user_can ( $post_type_obj -> cap -> edit_posts ) ) {
return true ;
2023-06-21 13:21:29 -04:00
} elseif ( isset ( $this -> query_args [ 'customer_id' ], $this -> source ) && $this -> source -> ID === $this -> query_args [ 'customer_id' ] ) {
return true ;
} elseif ( isset ( $this -> query_args [ 'billing_email' ], $this -> source ) && $this -> source -> email === $this -> query_args [ 'billing_email' ] ) {
return true ;
2020-01-13 13:08:10 -05:00
}
2023-06-21 13:21:29 -04:00
return $this -> should_execute ;
}
/**
* Sets whether or not the query should execute
*
* @param bool $should_execute Whether or not the query should execute.
*
* @return void
*/
public function set_should_execute ( bool $should_execute ) {
$this -> should_execute = $should_execute ;
2019-03-31 00:21:31 -04:00
}
/**
* Creates query arguments array
2023-06-21 13:21:29 -04:00
*
* @return array
2019-03-31 00:21:31 -04:00
*/
public function get_query_args () {
// Prepare for later use.
$last = ! empty ( $this -> args [ 'last' ] ) ? $this -> args [ 'last' ] : null ;
$first = ! empty ( $this -> args [ 'first' ] ) ? $this -> args [ 'first' ] : null ;
// Set the $query_args based on various defaults and primary input $args.
2022-08-26 14:53:36 -04:00
$query_args = [
2023-06-21 13:21:29 -04:00
'post_type' => $this -> post_type ,
2019-04-23 20:13:57 -04:00
'no_rows_found' => true ,
'return' => 'ids' ,
2021-01-21 22:41:43 -05:00
'limit' => min ( max ( absint ( $first ), absint ( $last ), 10 ), $this -> query_amount ) + 1 ,
2022-08-26 14:53:36 -04:00
];
2019-03-31 00:21:31 -04:00
2019-04-22 19:52:05 -04:00
/**
2023-06-21 13:21:29 -04:00
* Set posts_per_page the highest value of $first and $last, with a (filterable) max of 100
2019-04-22 19:52:05 -04:00
*/
2023-06-21 13:21:29 -04:00
$query_args [ 'posts_per_page' ] = $this -> one_to_one ? 1 : min ( max ( absint ( $first ), absint ( $last ), 10 ), $this -> query_amount ) + 1 ;
2019-04-22 19:52:05 -04:00
2019-04-23 20:13:57 -04:00
/**
2023-06-21 13:21:29 -04:00
* Set the graphql cursor args.
2019-04-23 20:13:57 -04:00
*/
2023-06-21 13:21:29 -04:00
$query_args [ 'graphql_cursor_compare' ] = ( ! empty ( $last ) ) ? '>' : '<' ;
$query_args [ 'graphql_after_cursor' ] = $this -> get_after_offset ();
$query_args [ 'graphql_before_cursor' ] = $this -> get_before_offset ();
/**
* If the cursor offsets not empty,
* ignore sticky posts on the query
*/
if ( ! empty ( $this -> get_after_offset () ) || ! empty ( $this -> get_after_offset () ) ) {
2019-04-23 20:13:57 -04:00
$query_args [ 'ignore_sticky_posts' ] = true ;
}
2023-06-21 13:21:29 -04:00
2019-04-23 20:13:57 -04:00
/**
* Pass the graphql $args to the WP_Query
*/
$query_args [ 'graphql_args' ] = $this -> args ;
/**
* Collect the input_fields and sanitize them to prepare them for sending to the WP_Query
*/
2022-08-26 14:53:36 -04:00
$input_fields = [];
2019-04-23 20:13:57 -04:00
if ( ! empty ( $this -> args [ 'where' ] ) ) {
$input_fields = $this -> sanitize_input_fields ( $this -> args [ 'where' ] );
}
if ( ! empty ( $input_fields ) ) {
$query_args = array_merge ( $query_args , $input_fields );
}
2019-04-10 12:26:37 -04:00
/**
* If there's no orderby params in the inputArgs, set order based on the first/last argument
*/
if ( empty ( $query_args [ 'orderby' ] ) ) {
2023-06-21 13:21:29 -04:00
$query_args [ 'order' ] = ! empty ( $last ) ? 'ASC' : 'DESC' ;
$query_args [ 'orderby' ] = [ 'date' => $query_args [ 'order' ] ];
2019-04-10 12:26:37 -04:00
}
/**
* Filter the $query args to allow folks to customize queries programmatically
*
2024-08-06 18:07:37 -04:00
* @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
2023-07-19 23:01:50 +03:00
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the GraphQL tree
2019-04-10 12:26:37 -04:00
*/
$query_args = apply_filters ( 'graphql_order_connection_query_args' , $query_args , $this -> source , $this -> args , $this -> context , $this -> info );
2019-03-31 00:21:31 -04:00
return $query_args ;
}
/**
* Executes query
*
2023-07-19 23:01:50 +03:00
* @throws \GraphQL\Error\InvariantViolation Filter currently not supported for WC_Order_Query.
2023-06-21 13:21:29 -04:00
*
* @return \WC_Order_Query
2019-03-31 00:21:31 -04:00
*/
public function get_query () {
2024-08-06 18:07:37 -04:00
/** @var array<string, mixed> $query_args */
$query_args = $this -> query_args ;
$query = new \WC_Order_Query ( $query_args );
2020-08-11 19:18:19 -04:00
if ( true === $query -> get ( 'suppress_filters' , false ) ) {
2026-06-30 10:16:21 -04:00
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.' , 'graphql-for-ecommerce' ) );
2020-08-11 19:18:19 -04:00
}
return $query ;
2019-03-31 00:21:31 -04:00
}
/**
2023-06-21 13:21:29 -04:00
* {@inheritDoc}
2019-03-31 00:21:31 -04:00
*/
2023-06-21 13:21:29 -04:00
public function get_ids_from_query () {
$ids = ! empty ( $this -> query -> get_orders () ) ? $this -> query -> get_orders () : [];
// If we're going backwards, we need to reverse the array.
if ( ! empty ( $this -> args [ 'last' ] ) ) {
$ids = array_reverse ( $ids );
}
return $ids ;
2019-03-31 00:21:31 -04:00
}
2019-04-09 21:40:23 -04:00
2019-09-29 15:11:02 -04:00
/**
* Returns meta keys to be used for connection ordering.
*
2023-12-27 11:28:24 -05:00
* @param bool $is_numeric Return numeric meta keys. Defaults to "true".
*
2019-09-29 15:11:02 -04:00
* @return array
*/
2023-12-27 11:28:24 -05:00
public function ordering_meta ( $is_numeric = true ) {
if ( ! $is_numeric ) {
return apply_filters (
'woographql_order_connection_orderby_meta_keys' ,
[
'_order_key' ,
'_date_paid' ,
'_date_completed' ,
]
);
}
return apply_filters (
'woographql_order_connection_orderby_numeric_meta_keys' ,
[
'_cart_discount' ,
'_order_total' ,
'_order_tax' ,
]
);
2019-09-29 15:11:02 -04:00
}
2019-04-09 21:40:23 -04:00
/**
* This sets up the "allowed" args, and translates the GraphQL-friendly keys to WP_Query
* friendly keys. There's probably a cleaner/more dynamic way to approach this, but
* this was quick. I'd be down to explore more dynamic ways to map this, but for
* now this gets the job done.
*
* @param array $where_args - arguments being used to filter query.
*
* @return array
*/
public function sanitize_input_fields ( array $where_args ) {
global $wpdb ;
2019-09-29 15:11:02 -04:00
$args = $this -> sanitize_common_inputs ( $where_args );
2019-04-09 21:40:23 -04:00
2022-08-26 14:53:36 -04:00
$key_mapping = [
2019-04-24 01:41:02 -04:00
'post_parent' => 'parent' ,
'post_parent__not_in' => 'parent_exclude' ,
'post__not_in' => 'exclude' ,
2022-08-26 14:53:36 -04:00
];
2019-04-24 01:41:02 -04:00
foreach ( $key_mapping as $key => $field ) {
if ( isset ( $args [ $key ] ) ) {
$args [ $field ] = $args [ $key ];
unset ( $args [ $key ] );
}
}
2019-04-09 21:40:23 -04:00
if ( ! empty ( $where_args [ 'statuses' ] ) ) {
2026-03-26 22:45:38 -04:00
$single_status = 1 === count ( $where_args [ 'statuses' ] );
$args [ 'status' ] = $single_status ? $where_args [ 'statuses' ][ 0 ] : $where_args [ 'statuses' ];
2019-04-09 21:40:23 -04:00
}
if ( ! empty ( $where_args [ 'customerId' ] ) ) {
2019-04-24 01:21:29 -04:00
$args [ 'customer_id' ] = $where_args [ 'customerId' ];
}
if ( ! empty ( $where_args [ 'customersIn' ] ) ) {
$args [ 'customer' ] = $where_args [ 'customersIn' ];
2019-04-09 21:40:23 -04:00
}
2023-07-31 15:28:39 -04:00
if ( ! empty ( $where_args [ 'billingEmail' ] ) ) {
$billing_email = $where_args [ 'billingEmail' ];
$args [ 'customer' ] = ! empty ( $args [ 'customer' ] )
? array_merge ( $args [ 'customer' ], [ $billing_email ] )
: $billing_email ;
}
2019-04-09 21:40:23 -04:00
// Search by product.
if ( ! empty ( $where_args [ 'productId' ] ) ) {
2022-08-26 14:53:36 -04:00
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
2019-04-09 21:40:23 -04:00
$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.
2022-08-26 14:53:36 -04:00
$args [ 'post__in' ] = ! empty ( $order_ids ) ? $order_ids : [ 0 ];
2019-04-09 21:40:23 -04:00
}
// Search.
if ( ! empty ( $args [ 's' ] ) ) {
$order_ids = wc_order_search ( $args [ 's' ] );
if ( ! empty ( $order_ids ) ) {
unset ( $args [ 's' ] );
2019-04-10 12:26:37 -04:00
$args [ 'post__in' ] = isset ( $args [ 'post__in' ] )
? array_intersect ( $order_ids , $args [ 'post__in' ] )
: $order_ids ;
2019-04-09 21:40:23 -04:00
}
}
2019-04-10 12:26:37 -04:00
/**
* 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
*
2024-08-06 18:07:37 -04:00
* @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
2023-07-19 23:01:50 +03:00
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object
2024-08-06 18:07:37 -04:00
* @param mixed|string|array $post_type The post type for the query
2019-04-10 12:26:37 -04:00
*/
$args = apply_filters (
'graphql_map_input_fields_to_order_query' ,
$args ,
$where_args ,
$this -> source ,
$this -> args ,
$this -> context ,
$this -> info ,
$this -> post_type
);
2019-04-09 21:40:23 -04:00
return $args ;
}
2020-01-13 14:24:11 -05:00
/**
2023-06-21 13:21:29 -04:00
* Determine whether or not the the offset is valid, i.e the order corresponding to the offset
* exists. Offset is equivalent to order_id. So this function is equivalent to checking if the
* post with the given ID exists.
2020-01-13 14:24:11 -05:00
*
2023-06-21 13:21:29 -04:00
* @param int $offset The ID of the node used in the cursor offset.
2020-01-13 14:24:11 -05:00
*
* @return bool
*/
public function is_valid_offset ( $offset ) {
2023-06-21 13:21:29 -04:00
return ( bool ) \wc_get_order ( absint ( $offset ) );
2020-01-13 14:24:11 -05:00
}
2019-03-31 00:21:31 -04:00
}