• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql-woocommerce / 5718813479

pending completion
5718813479

push

github

web-flow
docs: More docs drafted. (#774)

* docs: Pro docs drafted.

* feat: billingEmail added to OrderConnectionWhereArgs

* feat: More drafts added.

* docs: More documentation introduced.

* docs: page meta added to each new section

* chore: Linter compliance met

* docs: page meta added to each new section

* docs: Some revision and screenshots added.

* docs: More revisions

* feat: more revisions and images added.

* updated docs after proofreading

* chore: HPOS compatibility declared.

* docs: Some word consistency issues fixed.

* docs: Introductions and conclusions added.

* chore: Linter compliance met

---------

Co-authored-by: Craig Wilcox <craig@simplur.com>

14 of 14 new or added lines in 2 files covered. (100.0%)

10310 of 12437 relevant lines covered (82.9%)

54.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.91
/includes/data/connection/class-order-connection-resolver.php
1
<?php
2
/**
3
 * ConnectionResolver - Order_Connection_Resolver
4
 *
5
 * Resolves connections to Orders
6
 *
7
 * @package WPGraphQL\WooCommerce\Data\Connection
8
 * @since 0.0.1
9
 */
10

11
namespace WPGraphQL\WooCommerce\Data\Connection;
12

13
use GraphQL\Error\InvariantViolation;
14
use WPGraphQL\Data\Connection\AbstractConnectionResolver;
15

16
/**
17
 * Class Order_Connection_Resolver
18
 */
19
class Order_Connection_Resolver extends AbstractConnectionResolver {
20
        /**
21
         * Include CPT Loader connection common functions.
22
         */
23
        use WC_CPT_Loader_Common;
24

25
        /**
26
         * The name of the post type, or array of post types the connection resolver is resolving for
27
         *
28
         * @var string
29
         */
30
        protected $post_type;
31

32
        /**
33
         * This stores the should
34
         *
35
         * @var boolean
36
         */
37
        protected $should_execute = false;
38

39
        /**
40
         * Refund_Connection_Resolver constructor.
41
         *
42
         * @param mixed                                $source    The object passed down from the previous level in the Resolve tree.
43
         * @param array                                $args      The input arguments for the query.
44
         * @param \WPGraphQL\AppContext                $context   The context of the request.
45
         * @param \GraphQL\Type\Definition\ResolveInfo $info      The resolve info passed down the Resolve tree.
46
         * @param string                               $post_type The post type for the connection resolver.
47
         */
48
        public function __construct( $source, $args, $context, $info, $post_type = 'shop_order' ) {
49
                /**
50
                 * Set the post type for the resolver.
51
                 */
52
                $this->post_type = $post_type;
10✔
53

54
                /**
55
                 * Call the parent construct to setup class data.
56
                 */
57
                parent::__construct( $source, $args, $context, $info );
10✔
58

59
                /**
60
                 * Default to true.
61
                 */
62
                $this->should_execute = true;
10✔
63
        }
64

65
        /**
66
         * Return the name of the loader to be used with the connection resolver
67
         *
68
         * @return string
69
         */
70
        public function get_loader_name() {
71
                return 'wc_post';
10✔
72
        }
73

74
        /**
75
         * Given an ID, return the model for the entity or null
76
         *
77
         * @param integer $id  Node ID.
78
         *
79
         * @return mixed|\WPGraphQL\WooCommerce\Model\Order|null
80
         */
81
        public function get_node_by_id( $id ) {
82
                return $this->get_cpt_model_by_id( $id );
10✔
83
        }
84

85
        /**
86
         * Checks if user is authorized to query orders
87
         *
88
         * @return bool
89
         */
90
        public function should_execute() {
91
                /**
92
                 * Get order post type.
93
                 *
94
                 * @var \WP_Post_Type $post_type_obj
95
                 */
96
                $post_type_obj = get_post_type_object( $this->post_type );
10✔
97
                if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
10✔
98
                        return true;
5✔
99
                } elseif ( isset( $this->query_args['customer_id'], $this->source ) && $this->source->ID === $this->query_args['customer_id'] ) {
6✔
100
                        return true;
4✔
101
                } elseif ( isset( $this->query_args['billing_email'], $this->source ) && $this->source->email === $this->query_args['billing_email'] ) {
3✔
102
                        return true;
×
103
                }
104

105
                return $this->should_execute;
3✔
106
        }
107

108
        /**
109
         * Sets whether or not the query should execute
110
         *
111
         * @param bool $should_execute Whether or not the query should execute.
112
         *
113
         * @return void
114
         */
115
        public function set_should_execute( bool $should_execute ) {
116
                $this->should_execute = $should_execute;
7✔
117
        }
118

119
        /**
120
         * Creates query arguments array
121
         *
122
         * @return array
123
         */
124
        public function get_query_args() {
125
                // Prepare for later use.
126
                $last  = ! empty( $this->args['last'] ) ? $this->args['last'] : null;
10✔
127
                $first = ! empty( $this->args['first'] ) ? $this->args['first'] : null;
10✔
128

129
                // Set the $query_args based on various defaults and primary input $args.
130
                $query_args = [
10✔
131
                        'post_type'     => $this->post_type,
10✔
132
                        'no_rows_found' => true,
10✔
133
                        'return'        => 'ids',
10✔
134
                        'limit'         => min( max( absint( $first ), absint( $last ), 10 ), $this->query_amount ) + 1,
10✔
135
                ];
10✔
136

137
                /**
138
                 * Set posts_per_page the highest value of $first and $last, with a (filterable) max of 100
139
                 */
140
                $query_args['posts_per_page'] = $this->one_to_one ? 1 : min( max( absint( $first ), absint( $last ), 10 ), $this->query_amount ) + 1;
10✔
141

142
                /**
143
                 * Set the graphql cursor args.
144
                 */
145
                $query_args['graphql_cursor_compare'] = ( ! empty( $last ) ) ? '>' : '<';
10✔
146
                $query_args['graphql_after_cursor']   = $this->get_after_offset();
10✔
147
                $query_args['graphql_before_cursor']  = $this->get_before_offset();
10✔
148

149
                /**
150
                 * If the cursor offsets not empty,
151
                 * ignore sticky posts on the query
152
                 */
153
                if ( ! empty( $this->get_after_offset() ) || ! empty( $this->get_after_offset() ) ) {
10✔
154
                        $query_args['ignore_sticky_posts'] = true;
2✔
155
                }
156

157
                /**
158
                 * Pass the graphql $args to the WP_Query
159
                 */
160
                $query_args['graphql_args'] = $this->args;
10✔
161

162
                /**
163
                 * Collect the input_fields and sanitize them to prepare them for sending to the WP_Query
164
                 */
165
                $input_fields = [];
10✔
166
                if ( ! empty( $this->args['where'] ) ) {
10✔
167
                        $input_fields = $this->sanitize_input_fields( $this->args['where'] );
2✔
168
                }
169

170
                if ( ! empty( $input_fields ) ) {
10✔
171
                        $query_args = array_merge( $query_args, $input_fields );
2✔
172
                }
173

174
                /**
175
                 * If there's no orderby params in the inputArgs, set order based on the first/last argument
176
                 */
177
                if ( empty( $query_args['orderby'] ) ) {
10✔
178
                        $query_args['order']   = ! empty( $last ) ? 'ASC' : 'DESC';
10✔
179
                        $query_args['orderby'] = [ 'date' => $query_args['order'] ];
10✔
180
                }
181

182
                /**
183
                 * Filter the $query args to allow folks to customize queries programmatically
184
                 *
185
                 * @param array       $query_args The args that will be passed to the WP_Query
186
                 * @param mixed       $source     The source that's passed down the GraphQL queries
187
                 * @param array       $args       The inputArgs on the field
188
                 * @param \WPGraphQL\AppContext  $context    The AppContext passed down the GraphQL tree
189
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       The ResolveInfo passed down the GraphQL tree
190
                 */
191
                $query_args = apply_filters( 'graphql_order_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
10✔
192

193
                return $query_args;
10✔
194
        }
195

196
        /**
197
         * Executes query
198
         *
199
         * @throws \GraphQL\Error\InvariantViolation  Filter currently not supported for WC_Order_Query.
200
         *
201
         * @return \WC_Order_Query
202
         */
203
        public function get_query() {
204
                $query = new \WC_Order_Query( $this->query_args );
10✔
205

206
                if ( true === $query->get( 'suppress_filters', false ) ) {
10✔
207
                        throw new InvariantViolation( __( 'WC_Order_Query has been modified by a plugin or theme to suppress_filters, which will cause issues with WPGraphQL Execution. If you need to suppress filters for a specific reason within GraphQL, consider registering a custom field to the WPGraphQL Schema with a custom resolver.', 'wp-graphql-woocommerce' ) );
×
208
                }
209

210
                return $query;
10✔
211
        }
212

213
        /**
214
         * {@inheritDoc}
215
         */
216
        public function get_ids_from_query() {
217
                $ids = ! empty( $this->query->get_orders() ) ? $this->query->get_orders() : [];
10✔
218

219
                // If we're going backwards, we need to reverse the array.
220
                if ( ! empty( $this->args['last'] ) ) {
10✔
221
                        $ids = array_reverse( $ids );
2✔
222
                }
223

224
                return $ids;
10✔
225
        }
226

227
        /**
228
         * Returns meta keys to be used for connection ordering.
229
         *
230
         * @return array
231
         */
232
        public function ordering_meta() {
233
                return [
1✔
234
                        '_order_key',
1✔
235
                        '_cart_discount',
1✔
236
                        '_order_total',
1✔
237
                        '_order_tax',
1✔
238
                        '_date_paid',
1✔
239
                        '_date_completed',
1✔
240
                ];
1✔
241
        }
242

243
        /**
244
         * This sets up the "allowed" args, and translates the GraphQL-friendly keys to WP_Query
245
         * friendly keys. There's probably a cleaner/more dynamic way to approach this, but
246
         * this was quick. I'd be down to explore more dynamic ways to map this, but for
247
         * now this gets the job done.
248
         *
249
         * @param array $where_args - arguments being used to filter query.
250
         *
251
         * @return array
252
         */
253
        public function sanitize_input_fields( array $where_args ) {
254
                global $wpdb;
2✔
255
                $args = $this->sanitize_common_inputs( $where_args );
2✔
256

257
                $key_mapping = [
2✔
258
                        'post_parent'         => 'parent',
2✔
259
                        'post_parent__not_in' => 'parent_exclude',
2✔
260
                        'post__not_in'        => 'exclude',
2✔
261
                ];
2✔
262

263
                foreach ( $key_mapping as $key => $field ) {
2✔
264
                        if ( isset( $args[ $key ] ) ) {
2✔
265
                                $args[ $field ] = $args[ $key ];
×
266
                                unset( $args[ $key ] );
×
267
                        }
268
                }
269

270
                if ( ! empty( $where_args['statuses'] ) ) {
2✔
271
                        if ( 1 === count( $where_args ) ) {
2✔
272
                                $args['status'] = $where_args['statuses'][0];
1✔
273
                        } else {
274
                                $args['status'] = $where_args['statuses'];
1✔
275
                        }
276
                }
277

278
                if ( ! empty( $where_args['customerId'] ) ) {
2✔
279
                        $args['customer_id'] = $where_args['customerId'];
1✔
280
                }
281

282
                if ( ! empty( $where_args['customersIn'] ) ) {
2✔
283
                        $args['customer'] = $where_args['customersIn'];
1✔
284
                }
285
                if ( ! empty( $where_args['billingEmail'] ) ) {
2✔
286
                        $billing_email    = $where_args['billingEmail'];
1✔
287
                        $args['customer'] = ! empty( $args['customer'] )
1✔
288
                                ? array_merge( $args['customer'], [ $billing_email ] )
×
289
                                : $billing_email;
1✔
290
                }
291

292
                // Search by product.
293
                if ( ! empty( $where_args['productId'] ) ) {
2✔
294
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
295
                        $order_ids = $wpdb->get_col(
1✔
296
                                $wpdb->prepare(
1✔
297
                                        "SELECT order_id
1✔
298
                                        FROM {$wpdb->prefix}woocommerce_order_items
1✔
299
                                        WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
1✔
300
                                        AND order_item_type = 'line_item'",
1✔
301
                                        absint( $where_args['productId'] )
1✔
302
                                )
1✔
303
                        );
1✔
304

305
                        // Force WP_Query return empty if don't found any order.
306
                        $args['post__in'] = ! empty( $order_ids ) ? $order_ids : [ 0 ];
1✔
307
                }
308

309
                // Search.
310
                if ( ! empty( $args['s'] ) ) {
2✔
311
                        $order_ids = wc_order_search( $args['s'] );
×
312
                        if ( ! empty( $order_ids ) ) {
×
313
                                unset( $args['s'] );
×
314
                                $args['post__in'] = isset( $args['post__in'] )
×
315
                                ? array_intersect( $order_ids, $args['post__in'] )
×
316
                                : $order_ids;
×
317
                        }
318
                }
319

320
                /**
321
                 * Filter the input fields
322
                 * This allows plugins/themes to hook in and alter what $args should be allowed to be passed
323
                 * from a GraphQL Query to the WP_Query
324
                 *
325
                 * @param array       $args       The mapped query arguments
326
                 * @param array       $where_args Query "where" args
327
                 * @param mixed       $source     The query results for a query calling this
328
                 * @param array       $all_args   All of the arguments for the query (not just the "where" args)
329
                 * @param \WPGraphQL\AppContext  $context    The AppContext object
330
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       The ResolveInfo object
331
                 * @param mixed|string|array      $post_type  The post type for the query
332
                 */
333
                $args = apply_filters(
2✔
334
                        'graphql_map_input_fields_to_order_query',
2✔
335
                        $args,
2✔
336
                        $where_args,
2✔
337
                        $this->source,
2✔
338
                        $this->args,
2✔
339
                        $this->context,
2✔
340
                        $this->info,
2✔
341
                        $this->post_type
2✔
342
                );
2✔
343

344
                return $args;
2✔
345
        }
346

347
        /**
348
         * Determine whether or not the the offset is valid, i.e the order corresponding to the offset
349
         * exists. Offset is equivalent to order_id. So this function is equivalent to checking if the
350
         * post with the given ID exists.
351
         *
352
         * @param int $offset The ID of the node used in the cursor offset.
353
         *
354
         * @return bool
355
         */
356
        public function is_valid_offset( $offset ) {
357
                return (bool) \wc_get_order( absint( $offset ) );
2✔
358
        }
359
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc