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

wp-graphql / wp-graphql-woocommerce / 23663706692

27 Mar 2026 07:25PM UTC coverage: 89.424% (+0.3%) from 89.165%
23663706692

Pull #1002

github

web-flow
Merge 49b913dda into 66f840efc
Pull Request #1002: feat: WC Settings API, compatibility refactor, HPOS fix

575 of 643 new or added lines in 21 files covered. (89.42%)

15812 of 17682 relevant lines covered (89.42%)

284.73 hits per line

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

92.16
/includes/type/object/class-root-query.php
1
<?php
2
/**
3
 * Registers WooCommerce fields on the RootQuery object.
4
 *
5
 * @package WPGraphQL\WooCommerce\Type\WPObject
6
 * @since   0.6.0
7
 */
8

9
namespace WPGraphQL\WooCommerce\Type\WPObject;
10

11
use Automattic\WooCommerce\StoreApi\Utilities\ProductQueryFilters;
12
use Automattic\WooCommerce\Utilities\OrderUtil;
13
use GraphQL\Error\UserError;
14
use GraphQLRelay\Relay;
15
use WPGraphQL\AppContext;
16
use WPGraphQL\WooCommerce\Data\Factory;
17
use WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce as WooGraphQL;
18

19
/**
20
 * Class - Root_Query
21
 */
22
class Root_Query {
23
        /**
24
         * Registers WC-related root queries.
25
         *
26
         * @return void
27
         */
28
        public static function register_fields() {
29
                register_graphql_fields(
560✔
30
                        'RootQuery',
560✔
31
                        [
560✔
32
                                'cart'             => [
560✔
33
                                        'type'        => 'Cart',
560✔
34
                                        'args'        => [
560✔
35
                                                'recalculateTotals' => [
560✔
36
                                                        'type'        => 'Boolean',
560✔
37
                                                        'description' => __( 'Should cart totals be recalculated.', 'wp-graphql-woocommerce' ),
560✔
38
                                                ],
560✔
39
                                                'fees'              => [
560✔
40
                                                        'type'        => [ 'list_of' => 'FeeInput' ],
560✔
41
                                                        'description' => __( 'Fees to add to the cart.', 'wp-graphql-woocommerce' ),
560✔
42
                                                ],
560✔
43
                                        ],
560✔
44
                                        'description' => __( 'The cart object', 'wp-graphql-woocommerce' ),
560✔
45
                                        'resolve'     => static function ( $_, $args ) {
560✔
46
                                                $token_invalid = apply_filters( 'graphql_woocommerce_session_token_errors', null );
42✔
47
                                                if ( $token_invalid ) {
42✔
48
                                                        throw new UserError( $token_invalid );
2✔
49
                                                }
50

51
                                                $cart = Factory::resolve_cart();
40✔
52

53
                                                if ( ! empty( $args['fees'] ) ) {
40✔
54
                                                        $fees = $args['fees'];
2✔
55
                                                        add_action(
2✔
56
                                                                'woocommerce_cart_calculate_fees',
2✔
57
                                                                static function () use ( $fees ) {
2✔
58
                                                                        foreach ( $fees as $fee_input ) {
2✔
59
                                                                                if ( empty( $fee_input['name'] ) || empty( $fee_input['amount'] ) ) {
2✔
60
                                                                                        // TODO: Log invalid fee input.
61
                                                                                        continue;
×
62
                                                                                }
63

64
                                                                                $fee_args = [
2✔
65
                                                                                        $fee_input['name'],
2✔
66
                                                                                        $fee_input['amount'],
2✔
67
                                                                                        isset( $fee_input['taxable'] ) ? $fee_input['taxable'] : false,
2✔
68
                                                                                        isset( $fee_input['taxClass'] ) ? $fee_input['taxClass'] : '',
2✔
69
                                                                                ];
2✔
70

71
                                                                                \WC()->cart->add_fee( ...$fee_args );
2✔
72
                                                                        }
73
                                                                }
2✔
74
                                                        );
2✔
75
                                                }
76

77
                                                if ( ! empty( $args['recalculateTotals'] ) ) {
40✔
78
                                                        $cart->calculate_totals();
×
79
                                                }
80

81
                                                return $cart;
40✔
82
                                        },
560✔
83
                                ],
560✔
84
                                'cartItem'         => [
560✔
85
                                        'type'        => 'CartItem',
560✔
86
                                        'args'        => [
560✔
87
                                                'key' => [
560✔
88
                                                        'type' => [ 'non_null' => 'ID' ],
560✔
89
                                                ],
560✔
90
                                        ],
560✔
91
                                        'description' => __( 'The cart object', 'wp-graphql-woocommerce' ),
560✔
92
                                        'resolve'     => static function ( $source, array $args ) {
560✔
93
                                                $item = Factory::resolve_cart()->get_cart_item( $args['key'] );
2✔
94
                                                if ( empty( $item ) || empty( $item['key'] ) ) {
2✔
95
                                                        throw new UserError( __( 'Failed to retrieve cart item.', 'wp-graphql-woocommerce' ) );
×
96
                                                }
97

98
                                                return $item;
2✔
99
                                        },
560✔
100
                                ],
560✔
101
                                'cartFee'          => [
560✔
102
                                        'type'        => 'CartFee',
560✔
103
                                        'args'        => [
560✔
104
                                                'id' => [
560✔
105
                                                        'type' => [ 'non_null' => 'ID' ],
560✔
106
                                                ],
560✔
107
                                        ],
560✔
108
                                        'description' => __( 'The cart object', 'wp-graphql-woocommerce' ),
560✔
109
                                        'resolve'     => static function ( $source, array $args ) {
560✔
110
                                                $fees   = Factory::resolve_cart()->get_fees();
2✔
111
                                                $fee_id = $args['id'];
2✔
112

113
                                                if ( empty( $fees[ $fee_id ] ) ) {
2✔
114
                                                        throw new UserError( __( 'The ID input is invalid', 'wp-graphql-woocommerce' ) );
×
115
                                                }
116

117
                                                return $fees[ $fee_id ];
2✔
118
                                        },
560✔
119
                                ],
560✔
120
                                'coupon'           => [
560✔
121
                                        'type'        => 'Coupon',
560✔
122
                                        'description' => __( 'A coupon object', 'wp-graphql-woocommerce' ),
560✔
123
                                        'args'        => [
560✔
124
                                                'id'     => [ 'type' => [ 'non_null' => 'ID' ] ],
560✔
125
                                                'idType' => [
560✔
126
                                                        'type'        => 'CouponIdTypeEnum',
560✔
127
                                                        'description' => __( 'Type of ID being used identify coupon', 'wp-graphql-woocommerce' ),
560✔
128
                                                ],
560✔
129
                                        ],
560✔
130
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
131
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
8✔
132
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
8✔
133

134
                                                $coupon_id = null;
8✔
135
                                                switch ( $id_type ) {
136
                                                        case 'code':
8✔
137
                                                                $coupon_id = \wc_get_coupon_id_by_code( $id );
2✔
138
                                                                break;
2✔
139
                                                        case 'database_id':
8✔
140
                                                                $coupon_id = absint( $id );
2✔
141
                                                                break;
2✔
142
                                                        case 'global_id':
8✔
143
                                                        default:
144
                                                                $id_components = Relay::fromGlobalId( $args['id'] );
8✔
145
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
8✔
146
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
147
                                                                }
148
                                                                $coupon_id = absint( $id_components['id'] );
8✔
149
                                                                break;
8✔
150
                                                }
151

152
                                                // Check if user authorized to view coupon.
153
                                                /**
154
                                                 * Get coupon post type.
155
                                                 *
156
                                                 * @var \WP_Post_Type $post_type
157
                                                 */
158
                                                $post_type     = get_post_type_object( 'shop_coupon' );
8✔
159
                                                $is_authorized = current_user_can( $post_type->cap->edit_others_posts );
8✔
160
                                                if ( ! $is_authorized ) {
8✔
161
                                                        return null;
2✔
162
                                                }
163

164
                                                if ( empty( $coupon_id ) ) {
8✔
165
                                                        /* translators: %1$s: ID type, %2$s: ID value */
166
                                                        throw new UserError( sprintf( __( 'No coupon ID was found corresponding to the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
167
                                                }
168

169
                                                $coupon = get_post( $coupon_id );
8✔
170
                                                if ( ! is_object( $coupon ) || 'shop_coupon' !== $coupon->post_type ) {
8✔
171
                                                        /* translators: %1$s: ID type, %2$s: ID value */
172
                                                        throw new UserError( sprintf( __( 'No coupon exists with the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
173
                                                }
174

175
                                                return Factory::resolve_crud_object( $coupon_id, $context );
8✔
176
                                        },
560✔
177
                                ],
560✔
178
                                'customer'         => [
560✔
179
                                        'type'        => 'Customer',
560✔
180
                                        'description' => __( 'A customer object', 'wp-graphql-woocommerce' ),
560✔
181
                                        'args'        => [
560✔
182
                                                'id'         => [
560✔
183
                                                        'type'        => 'ID',
560✔
184
                                                        'description' => __( 'Get the customer by their global ID', 'wp-graphql-woocommerce' ),
560✔
185
                                                ],
560✔
186
                                                'customerId' => [
560✔
187
                                                        'type'        => 'Int',
560✔
188
                                                        'description' => __( 'Get the customer by their database ID', 'wp-graphql-woocommerce' ),
560✔
189
                                                ],
560✔
190
                                        ],
560✔
191
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
192
                                                $current_user_id = get_current_user_id();
42✔
193

194
                                                // Default the customer to the current user.
195
                                                $customer_id = $current_user_id;
42✔
196

197
                                                // If a customer ID has been provided, resolve to that ID instead.
198
                                                if ( ! empty( $args['id'] ) ) {
42✔
199
                                                        $id_components = Relay::fromGlobalId( $args['id'] );
6✔
200
                                                        if ( ! isset( $id_components['id'] ) || ! absint( $id_components['id'] ) ) {
6✔
201
                                                                throw new UserError( __( 'The ID input is invalid', 'wp-graphql-woocommerce' ) );
×
202
                                                        }
203

204
                                                        $customer_id = absint( $id_components['id'] );
6✔
205
                                                } elseif ( ! empty( $args['customerId'] ) ) {
42✔
206
                                                        $customer_id = absint( $args['customerId'] );
2✔
207
                                                }
208

209
                                                // If a user does not have the ability to list users, they can only view their own customer object.
210
                                                $unauthorized = ! empty( $customer_id )
42✔
211
                                                        && ! current_user_can( 'list_users' )
42✔
212
                                                        && $current_user_id !== $customer_id;
42✔
213
                                                if ( $unauthorized ) {
42✔
214
                                                        throw new UserError( __( 'Not authorized to access this customer', 'wp-graphql-woocommerce' ) );
4✔
215
                                                }
216

217
                                                // If we have a customer ID, resolve to that customer.
218
                                                if ( $customer_id ) {
42✔
219
                                                        return Factory::resolve_customer( $customer_id, $context );
38✔
220
                                                }
221

222
                                                // Resolve to the session customer.
223
                                                return Factory::resolve_session_customer();
6✔
224
                                        },
560✔
225
                                ],
560✔
226
                                'order'            => [
560✔
227
                                        'type'        => 'Order',
560✔
228
                                        'description' => __( 'A order object', 'wp-graphql-woocommerce' ),
560✔
229
                                        'args'        => [
560✔
230
                                                'id'     => [
560✔
231
                                                        'type'        => 'ID',
560✔
232
                                                        'description' => __( 'The ID for identifying the order', 'wp-graphql-woocommerce' ),
560✔
233
                                                ],
560✔
234
                                                'idType' => [
560✔
235
                                                        'type'        => 'OrderIdTypeEnum',
560✔
236
                                                        'description' => __( 'Type of ID being used identify order', 'wp-graphql-woocommerce' ),
560✔
237
                                                ],
560✔
238
                                        ],
560✔
239
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
240
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
24✔
241
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
24✔
242

243
                                                $order_id = null;
24✔
244
                                                switch ( $id_type ) {
245
                                                        case 'order_key':
24✔
246
                                                                $order_id = \wc_get_order_id_by_order_key( $id );
2✔
247
                                                                break;
2✔
248
                                                        case 'database_id':
24✔
249
                                                                $order_id = absint( $id );
4✔
250
                                                                break;
4✔
251
                                                        case 'global_id':
22✔
252
                                                        default:
253
                                                                $id_components = Relay::fromGlobalId( $id );
22✔
254
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
22✔
255
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
256
                                                                }
257
                                                                $order_id = absint( $id_components['id'] );
22✔
258
                                                                break;
22✔
259
                                                }
260

261
                                                if ( empty( $order_id ) ) {
24✔
262
                                                        /* translators: %1$s: ID type, %2$s: ID value */
263
                                                        throw new UserError( sprintf( __( 'No order ID was found corresponding to the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
264
                                                }
265

266
                                                if ( 'shop_order' !== OrderUtil::get_order_type( $order_id ) ) {
24✔
267
                                                        /* translators: %1$s: ID type, %2$s: ID value */
268
                                                        throw new UserError( sprintf( __( 'No order exists with the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
269
                                                }
270

271
                                                // Check if user authorized to view order.
272
                                                /**
273
                                                 * Get order post type.
274
                                                 *
275
                                                 * @var \WP_Post_Type $post_type
276
                                                 */
277
                                                $post_type     = get_post_type_object( 'shop_order' );
24✔
278
                                                $is_authorized = current_user_can( $post_type->cap->edit_others_posts );
24✔
279
                                                if ( ! $is_authorized && get_current_user_id() ) {
24✔
280
                                                        /** @var \WC_Order[] $orders */
281
                                                        $orders = wc_get_orders(
6✔
282
                                                                [
6✔
283
                                                                        'type'          => 'shop_order',
6✔
284
                                                                        'post__in'      => [ $order_id ],
6✔
285
                                                                        'customer_id'   => get_current_user_id(),
6✔
286
                                                                        'no_rows_found' => true,
6✔
287
                                                                        'return'        => 'ids',
6✔
288
                                                                ]
6✔
289
                                                        );
6✔
290

291
                                                        if ( in_array( $order_id, $orders, true ) ) {
6✔
292
                                                                $is_authorized = true;
4✔
293
                                                        }
294
                                                }
295

296
                                                // Throw if authorized to view order.
297
                                                if ( ! $is_authorized ) {
24✔
298
                                                        throw new UserError( __( 'Not authorized to access this order', 'wp-graphql-woocommerce' ) );
2✔
299
                                                }
300

301
                                                return Factory::resolve_crud_object( $order_id, $context );
24✔
302
                                        },
560✔
303
                                ],
560✔
304
                                'productVariation' => [
560✔
305
                                        'type'        => 'ProductVariation',
560✔
306
                                        'description' => __( 'A product variation object', 'wp-graphql-woocommerce' ),
560✔
307
                                        'args'        => [
560✔
308
                                                'id'     => [
560✔
309
                                                        'type'        => 'ID',
560✔
310
                                                        'description' => __( 'The ID for identifying the product variation', 'wp-graphql-woocommerce' ),
560✔
311
                                                ],
560✔
312
                                                'idType' => [
560✔
313
                                                        'type'        => 'ProductVariationIdTypeEnum',
560✔
314
                                                        'description' => __( 'Type of ID being used identify product variation', 'wp-graphql-woocommerce' ),
560✔
315
                                                ],
560✔
316
                                        ],
560✔
317
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
318
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
16✔
319
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
16✔
320

321
                                                $variation_id = null;
16✔
322
                                                switch ( $id_type ) {
323
                                                        case 'database_id':
16✔
324
                                                                $variation_id = absint( $id );
4✔
325
                                                                break;
4✔
326
                                                        case 'global_id':
14✔
327
                                                        default:
328
                                                                $id_components = Relay::fromGlobalId( $id );
14✔
329
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
14✔
330
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
331
                                                                }
332
                                                                $variation_id = absint( $id_components['id'] );
14✔
333
                                                                break;
14✔
334
                                                }
335

336
                                                if ( empty( $variation_id ) ) {
16✔
337
                                                        /* translators: %1$s: ID type, %2$s: ID value */
338
                                                        throw new UserError( sprintf( __( 'No product variation ID was found corresponding to the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
339
                                                }
340

341
                                                $variation = get_post( $variation_id );
16✔
342
                                                if ( ! is_object( $variation ) || 'product_variation' !== $variation->post_type ) {
16✔
343
                                                        /* translators: %1$s: ID type, %2$s: ID value */
344
                                                        throw new UserError( sprintf( __( 'No product variation exists with the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
345
                                                }
346

347
                                                return Factory::resolve_crud_object( $variation_id, $context );
16✔
348
                                        },
560✔
349
                                ],
560✔
350
                                'refund'           => [
560✔
351
                                        'type'        => 'Refund',
560✔
352
                                        'description' => __( 'A refund object', 'wp-graphql-woocommerce' ),
560✔
353
                                        'args'        => [
560✔
354
                                                'id'     => [
560✔
355
                                                        'type'        => [ 'non_null' => 'ID' ],
560✔
356
                                                        'description' => __( 'The ID for identifying the refund', 'wp-graphql-woocommerce' ),
560✔
357
                                                ],
560✔
358
                                                'idType' => [
560✔
359
                                                        'type'        => 'RefundIdTypeEnum',
560✔
360
                                                        'description' => __( 'Type of ID being used identify refund', 'wp-graphql-woocommerce' ),
560✔
361
                                                ],
560✔
362
                                        ],
560✔
363
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
364
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
6✔
365
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
6✔
366

367
                                                $refund_id = null;
6✔
368
                                                switch ( $id_type ) {
369
                                                        case 'database_id':
6✔
370
                                                                $refund_id = absint( $id );
2✔
371
                                                                break;
2✔
372
                                                        case 'global_id':
6✔
373
                                                        default:
374
                                                                $id_components = Relay::fromGlobalId( $id );
6✔
375
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
6✔
376
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
377
                                                                }
378
                                                                $refund_id = absint( $id_components['id'] );
6✔
379
                                                                break;
6✔
380
                                                }
381

382
                                                if ( empty( $refund_id ) ) {
6✔
383
                                                        /* translators: %1$s: ID type, %2$s: ID value */
384
                                                        throw new UserError( sprintf( __( 'No refund ID was found corresponding to the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
385
                                                }
386

387
                                                if ( 'shop_order_refund' !== OrderUtil::get_order_type( $refund_id ) ) {
6✔
388
                                                        /* translators: %1$s: ID type, %2$s: ID value */
389
                                                        throw new UserError( sprintf( __( 'No refund exists with the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $id ) );
×
390
                                                }
391

392
                                                // Check if user authorized to view order.
393
                                                /**
394
                                                 * Get refund post type.
395
                                                 *
396
                                                 * @var \WP_Post_Type $post_type
397
                                                 */
398
                                                $post_type     = get_post_type_object( 'shop_order_refund' );
6✔
399
                                                $is_authorized = current_user_can( $post_type->cap->edit_others_posts );
6✔
400
                                                if ( get_current_user_id() ) {
6✔
401
                                                        $refund = \wc_get_order( $refund_id );
6✔
402
                                                        if ( ! is_object( $refund ) || ! is_a( $refund, \WC_Order_Refund::class ) ) {
6✔
403
                                                                throw new UserError( __( 'Failed to retrieve refund', 'wp-graphql-woocommerce' ) );
×
404
                                                        }
405
                                                        $order_id = $refund->get_parent_id();
6✔
406

407
                                                        /** @var \WC_Order[] $orders */
408
                                                        $orders = wc_get_orders(
6✔
409
                                                                [
6✔
410
                                                                        'type'          => 'shop_order',
6✔
411
                                                                        'post__in'      => [ $order_id ],
6✔
412
                                                                        'customer_id'   => get_current_user_id(),
6✔
413
                                                                        'no_rows_found' => true,
6✔
414
                                                                        'return'        => 'ids',
6✔
415
                                                                ]
6✔
416
                                                        );
6✔
417

418
                                                        if ( in_array( $order_id, $orders, true ) ) {
6✔
419
                                                                $is_authorized = true;
6✔
420
                                                        }
421
                                                }//end if
422

423
                                                // Throw if authorized to view refund.
424
                                                if ( ! $is_authorized ) {
6✔
425
                                                        throw new UserError( __( 'Not authorized to access this refund', 'wp-graphql-woocommerce' ) );
2✔
426
                                                }
427

428
                                                return Factory::resolve_crud_object( $refund_id, $context );
6✔
429
                                        },
560✔
430
                                ],
560✔
431
                                'shippingMethod'   => [
560✔
432
                                        'type'        => 'ShippingMethod',
560✔
433
                                        'description' => __( 'A shipping method object', 'wp-graphql-woocommerce' ),
560✔
434
                                        'args'        => [
560✔
435
                                                'id'     => [
560✔
436
                                                        'type'        => 'ID',
560✔
437
                                                        'description' => __( 'The ID for identifying the shipping method', 'wp-graphql-woocommerce' ),
560✔
438
                                                ],
560✔
439
                                                'idType' => [
560✔
440
                                                        'type'        => 'ShippingMethodIdTypeEnum',
560✔
441
                                                        'description' => __( 'Type of ID being used identify product variation', 'wp-graphql-woocommerce' ),
560✔
442
                                                ],
560✔
443
                                        ],
560✔
444
                                        'resolve'     => static function ( $source, array $args ) {
560✔
445
                                                if ( ! \wc_rest_check_manager_permissions( 'shipping_methods', 'read' ) ) {
2✔
446
                                                        throw new UserError( __( 'Sorry, you cannot view shipping methods.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
2✔
447
                                                }
448

449
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
2✔
450
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
2✔
451

452
                                                $method_id = null;
2✔
453
                                                switch ( $id_type ) {
454
                                                        case 'database_id':
2✔
455
                                                                $method_id = $id;
2✔
456
                                                                break;
2✔
457
                                                        case 'global_id':
2✔
458
                                                        default:
459
                                                                $id_components = Relay::fromGlobalId( $id );
2✔
460
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
2✔
461
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
462
                                                                }
463
                                                                $method_id = $id_components['id'];
2✔
464
                                                                break;
2✔
465
                                                }
466

467
                                                return Factory::resolve_shipping_method( $method_id );
2✔
468
                                        },
560✔
469
                                ],
560✔
470
                                'shippingZone'     => [
560✔
471
                                        'type'        => 'ShippingZone',
560✔
472
                                        'description' => __( 'A shipping zone object', 'wp-graphql-woocommerce' ),
560✔
473
                                        'args'        => [
560✔
474
                                                'id'     => [
560✔
475
                                                        'type'        => 'ID',
560✔
476
                                                        'description' => __( 'The ID for identifying the shipping zone', 'wp-graphql-woocommerce' ),
560✔
477
                                                ],
560✔
478
                                                'idType' => [
560✔
479
                                                        'type'        => 'ShippingZoneIdTypeEnum',
560✔
480
                                                        'description' => __( 'Type of ID being used identify shipping zone', 'wp-graphql-woocommerce' ),
560✔
481
                                                ],
560✔
482
                                        ],
560✔
483
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
484
                                                if ( ! \wc_shipping_enabled() ) {
2✔
485
                                                        throw new UserError( __( 'Shipping is disabled.', 'wp-graphql-woocommerce' ), 404 );
×
486
                                                }
487

488
                                                if ( ! \wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
2✔
489
                                                        throw new UserError( __( 'Permission denied.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
2✔
490
                                                }
491

492
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
2✔
493
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
2✔
494

495
                                                $zone_id = null;
2✔
496
                                                switch ( $id_type ) {
497
                                                        case 'database_id':
2✔
498
                                                                $zone_id = $id;
×
499
                                                                break;
×
500
                                                        case 'global_id':
2✔
501
                                                        default:
502
                                                                $id_components = Relay::fromGlobalId( $id );
2✔
503
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
2✔
504
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
505
                                                                }
506
                                                                $zone_id = $id_components['id'];
2✔
507
                                                                break;
2✔
508
                                                }
509

510
                                                return $context->get_loader( 'shipping_zone' )->load( $zone_id );
2✔
511
                                        },
560✔
512
                                ],
560✔
513
                                'taxRate'          => [
560✔
514
                                        'type'        => 'TaxRate',
560✔
515
                                        'description' => __( 'A tax rate object', 'wp-graphql-woocommerce' ),
560✔
516
                                        'args'        => [
560✔
517
                                                'id'     => [
560✔
518
                                                        'type'        => 'ID',
560✔
519
                                                        'description' => __( 'The ID for identifying the tax rate', 'wp-graphql-woocommerce' ),
560✔
520
                                                ],
560✔
521
                                                'idType' => [
560✔
522
                                                        'type'        => 'TaxRateIdTypeEnum',
560✔
523
                                                        'description' => __( 'Type of ID being used identify tax rate', 'wp-graphql-woocommerce' ),
560✔
524
                                                ],
560✔
525
                                        ],
560✔
526
                                        'resolve'     => static function ( $source, array $args, AppContext $context ) {
560✔
527
                                                if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
2✔
528
                                                        throw new UserError( __( 'Sorry, you cannot view tax rates.', 'wp-graphql-woocommerce' ), \rest_authorization_required_code() );
2✔
529
                                                }
530
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
2✔
531
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
2✔
532

533
                                                $rate_id = null;
2✔
534
                                                switch ( $id_type ) {
535
                                                        case 'database_id':
2✔
536
                                                                $rate_id = absint( $id );
2✔
537
                                                                break;
2✔
538
                                                        case 'global_id':
2✔
539
                                                        default:
540
                                                                $id_components = Relay::fromGlobalId( $id );
2✔
541
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
2✔
542
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
543
                                                                }
544
                                                                $rate_id = absint( $id_components['id'] );
2✔
545
                                                                break;
2✔
546
                                                }
547

548
                                                return Factory::resolve_tax_rate( $rate_id, $context );
2✔
549
                                        },
560✔
550
                                ],
560✔
551
                                'countries'        => [
560✔
552
                                        'type'        => [ 'list_of' => 'CountriesEnum' ],
560✔
553
                                        'description' => __( 'Countries', 'wp-graphql-woocommerce' ),
560✔
554
                                        'resolve'     => static function () {
560✔
555
                                                $wc_countries = new \WC_Countries();
2✔
556
                                                $countries    = $wc_countries->get_countries();
2✔
557

558
                                                return array_keys( $countries );
2✔
559
                                        },
560✔
560
                                ],
560✔
561
                                'allowedCountries' => [
560✔
562
                                        'type'        => [ 'list_of' => 'CountriesEnum' ],
560✔
563
                                        'description' => __( 'Countries that the store sells to', 'wp-graphql-woocommerce' ),
560✔
564
                                        'resolve'     => static function () {
560✔
565
                                                $wc_countries = new \WC_Countries();
2✔
566
                                                $countries    = $wc_countries->get_allowed_countries();
2✔
567

568
                                                return array_keys( $countries );
2✔
569
                                        },
560✔
570
                                ],
560✔
571
                                'countryStates'    => [
560✔
572
                                        'type'        => [ 'list_of' => 'CountryState' ],
560✔
573
                                        'args'        => [
560✔
574
                                                'country' => [
560✔
575
                                                        'type'        => [ 'non_null' => 'CountriesEnum' ],
560✔
576
                                                        'description' => __( 'Target country', 'wp-graphql-woocommerce' ),
560✔
577
                                                ],
560✔
578
                                        ],
560✔
579
                                        'description' => __( 'Countries that the store sells to', 'wp-graphql-woocommerce' ),
560✔
580
                                        'resolve'     => static function ( $_, $args ) {
560✔
581
                                                $country      = $args['country'];
2✔
582
                                                $wc_countries = new \WC_Countries();
2✔
583
                                                $states       = $wc_countries->get_shipping_country_states();
2✔
584

585
                                                if ( ! empty( $states ) && ! empty( $states[ $country ] ) ) {
2✔
586
                                                        $formatted_states = [];
2✔
587
                                                        foreach ( $states[ $country ] as $code => $name ) {
2✔
588
                                                                $formatted_states[] = compact( 'name', 'code' );
2✔
589
                                                        }
590

591
                                                        return $formatted_states;
2✔
592
                                                }
593

594
                                                return [];
×
595
                                        },
560✔
596
                                ],
560✔
597
                                'wcSettingGroups'  => [
560✔
598
                                        'type'        => [ 'list_of' => 'WCSettingGroup' ],
560✔
599
                                        'description' => __( 'WooCommerce setting groups', 'wp-graphql-woocommerce' ),
560✔
600
                                        'resolve'     => static function () {
560✔
601
                                                if ( ! \wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
6✔
602
                                                        throw new UserError( __( 'Sorry, you cannot view settings.', 'wp-graphql-woocommerce' ) );
2✔
603
                                                }
604

605
                                                $groups = apply_filters( 'woocommerce_settings_groups', [] ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
4✔
606
                                                return array_values( $groups );
4✔
607
                                        },
560✔
608
                                ],
560✔
609
                                'wcSettings'       => [
560✔
610
                                        'type'        => [ 'list_of' => 'WCSetting' ],
560✔
611
                                        'description' => __( 'WooCommerce settings for a specific group', 'wp-graphql-woocommerce' ),
560✔
612
                                        'args'        => [
560✔
613
                                                'group' => [
560✔
614
                                                        'type'        => [ 'non_null' => 'String' ],
560✔
615
                                                        'description' => __( 'Settings group ID', 'wp-graphql-woocommerce' ),
560✔
616
                                                ],
560✔
617
                                        ],
560✔
618
                                        'resolve'     => static function ( $_, $args ) {
560✔
619
                                                if ( ! \wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
6✔
NEW
620
                                                        throw new UserError( __( 'Sorry, you cannot view settings.', 'wp-graphql-woocommerce' ) );
×
621
                                                }
622

623
                                                $controller = new \WC_REST_Setting_Options_Controller();
6✔
624
                                                $settings   = $controller->get_group_settings( $args['group'] );
6✔
625

626
                                                if ( is_wp_error( $settings ) ) {
6✔
627
                                                        throw new UserError( $settings->get_error_message() );
2✔
628
                                                }
629

630
                                                return $settings;
4✔
631
                                        },
560✔
632
                                ],
560✔
633
                                'collectionStats'  => [
560✔
634
                                        'type'        => 'CollectionStats',
560✔
635
                                        'args'        => [
560✔
636
                                                'calculatePriceRange'        => [
560✔
637
                                                        'type'        => 'Boolean',
560✔
638
                                                        'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'wp-graphql-woocommerce' ),
560✔
639
                                                ],
560✔
640
                                                'calculateRatingCounts'      => [
560✔
641
                                                        'type'        => 'Boolean',
560✔
642
                                                        'description' => __( 'If true, calculates rating counts for products in the collection.', 'wp-graphql-woocommerce' ),
560✔
643
                                                ],
560✔
644
                                                'calculateStockStatusCounts' => [
560✔
645
                                                        'type'        => 'Boolean',
560✔
646
                                                        'description' => __( 'If true, calculates stock counts for products in the collection.', 'wp-graphql-woocommerce' ),
560✔
647
                                                ],
560✔
648
                                                'taxonomies'                 => [
560✔
649
                                                        'type' => [ 'list_of' => 'CollectionStatsQueryInput' ],
560✔
650
                                                ],
560✔
651
                                                'where'                      => [
560✔
652
                                                        'type' => 'CollectionStatsWhereArgs',
560✔
653
                                                ],
560✔
654
                                        ],
560✔
655
                                        'description' => __( 'Statistics for a product taxonomy query', 'wp-graphql-woocommerce' ),
560✔
656
                                        'resolve'     => static function ( $_, $args ) {
560✔
657
                                                /** @var array<string, mixed> $data */
658
                                                $data    = [
8✔
659
                                                        'min_price'           => null,
8✔
660
                                                        'max_price'           => null,
8✔
661
                                                        'attribute_counts'    => null,
8✔
662
                                                        'stock_status_counts' => null,
8✔
663
                                                        'rating_counts'       => null,
8✔
664
                                                ];
8✔
665
                                                $filters = new ProductQueryFilters();
8✔
666

667
                                                // Process client-side filters.
668
                                                $request = Collection_Stats_Type::prepare_rest_request( $args['where'] ?? [] );
8✔
669

670
                                                // Format taxonomies.
671
                                                if ( ! empty( $args['taxonomies'] ) ) {
8✔
672
                                                        $calculate_attribute_counts = [];
8✔
673
                                                        foreach ( $args['taxonomies'] as $attribute_to_count ) {
8✔
674
                                                                $attribute = [ 'taxonomy' => $attribute_to_count['taxonomy'] ];
8✔
675
                                                                // Set the query type.
676
                                                                if ( ! empty( $attribute_to_count['relation'] ) ) {
8✔
677
                                                                        $attribute['query_type'] = strtolower( $attribute_to_count['relation'] );
8✔
678
                                                                }
679

680
                                                                // Add the attribute to the list of attributes to count.
681
                                                                $calculate_attribute_counts[] = $attribute;
8✔
682
                                                        }
683

684
                                                        // Set the attribute counts to calculate.
685
                                                        $request->set_param( 'calculate_attribute_counts', $calculate_attribute_counts );
8✔
686
                                                }
687

688
                                                $request->set_param( 'calculate_price_range', $args['calculatePriceRange'] ?? false );
8✔
689
                                                $request->set_param( 'calculate_stock_status_counts', $args['calculateStockStatusCounts'] ?? false );
8✔
690
                                                $request->set_param( 'calculate_rating_counts', $args['calculateRatingCounts'] ?? false );
8✔
691

692
                                                if ( ! empty( $request['calculate_price_range'] ) ) {
8✔
693
                                                        /**
694
                                                         * A Rest request object for external filtering
695
                                                         *
696
                                                         * @var \WP_REST_Request $filter_request
697
                                                         */
698
                                                        $filter_request = clone $request;
2✔
699
                                                        $filter_request->set_param( 'min_price', null );
2✔
700
                                                        $filter_request->set_param( 'max_price', null );
2✔
701

702
                                                        /** @var object{min_price: float, max_price: float} */
703
                                                        $price_results     = $filters->get_filtered_price( $filter_request );
2✔
704
                                                        $data['min_price'] = $price_results->min_price;
2✔
705
                                                        $data['max_price'] = $price_results->max_price;
2✔
706
                                                }
707

708
                                                if ( ! empty( $request['calculate_stock_status_counts'] ) ) {
8✔
709
                                                        /**
710
                                                         * A Rest request object for external filtering
711
                                                         *
712
                                                         * @var \WP_REST_Request $filter_request
713
                                                         */
714
                                                        $filter_request = clone $request;
2✔
715
                                                        $counts         = $filters->get_stock_status_counts( $filter_request );
2✔
716

717
                                                        $data['stock_status_counts'] = [];
2✔
718

719
                                                        foreach ( $counts as $key => $value ) {
2✔
720
                                                                $data['stock_status_counts'][] = (object) [
2✔
721
                                                                        'status' => $key,
2✔
722
                                                                        'count'  => $value,
2✔
723
                                                                ];
2✔
724
                                                        }
725
                                                }
726

727
                                                if ( ! empty( $request['calculate_attribute_counts'] ) ) {
8✔
728
                                                        $taxonomy__or_queries  = [];
8✔
729
                                                        $taxonomy__and_queries = [];
8✔
730
                                                        foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) {
8✔
731
                                                                if ( ! isset( $attributes_to_count['taxonomy'] ) ) {
8✔
732
                                                                        continue;
×
733
                                                                }
734

735
                                                                if ( empty( $attributes_to_count['query_type'] ) || 'or' === $attributes_to_count['query_type'] ) {
8✔
736
                                                                        $taxonomy__or_queries[] = $attributes_to_count['taxonomy'];
6✔
737
                                                                } else {
738
                                                                        $taxonomy__and_queries[] = $attributes_to_count['taxonomy'];
6✔
739
                                                                }
740
                                                        }
741

742
                                                        $data['attribute_counts'] = [];
8✔
743
                                                        if ( ! empty( $taxonomy__or_queries ) ) {
8✔
744
                                                                foreach ( $taxonomy__or_queries as $taxonomy ) {
6✔
745
                                                                        /**
746
                                                                         * A Rest request object for external filtering
747
                                                                         *
748
                                                                         * @var \WP_REST_Request $filter_request
749
                                                                         */
750
                                                                        $filter_request    = clone $request;
6✔
751
                                                                        $filter_attributes = $filter_request->get_param( 'attributes' );
6✔
752

753
                                                                        if ( ! empty( $filter_attributes ) ) {
6✔
754
                                                                                $filter_attributes = array_filter(
×
755
                                                                                        $filter_attributes,
×
756
                                                                                        static function ( $query ) use ( $taxonomy ) {
×
757
                                                                                                return $query['attribute'] !== $taxonomy;
×
758
                                                                                        }
×
759
                                                                                );
×
760
                                                                        }
761

762
                                                                        $filter_request->set_param( 'attributes', $filter_attributes );
6✔
763
                                                                        $counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] );
6✔
764

765
                                                                        $data['attribute_counts'][ $taxonomy ] = [];
6✔
766
                                                                        foreach ( $counts as $key => $value ) {
6✔
767
                                                                                $data['attribute_counts'][ $taxonomy ][] = (object) [
6✔
768
                                                                                        'taxonomy' => $taxonomy,
6✔
769
                                                                                        'termId'   => $key,
6✔
770
                                                                                        'count'    => $value,
6✔
771
                                                                                ];
6✔
772
                                                                        }
773
                                                                }
774
                                                        }
775

776
                                                        if ( ! empty( $taxonomy__and_queries ) ) {
8✔
777
                                                                $counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries );
6✔
778

779
                                                                foreach ( $taxonomy__and_queries as $taxonomy ) {
6✔
780
                                                                        $data['attribute_counts'][ $taxonomy ] = [];
6✔
781
                                                                        foreach ( $counts as $key => $value ) {
6✔
782
                                                                                $data['attribute_counts'][ $taxonomy ][] = (object) [
6✔
783
                                                                                        'taxonomy' => $taxonomy,
6✔
784
                                                                                        'termId'   => $key,
6✔
785
                                                                                        'count'    => $value,
6✔
786
                                                                                ];
6✔
787
                                                                        }
788
                                                                }
789
                                                        }
790
                                                }
791

792
                                                if ( ! empty( $request['calculate_rating_counts'] ) ) {
8✔
793
                                                        /**
794
                                                         * A Rest request object for external filtering
795
                                                         *
796
                                                         * @var \WP_REST_Request $filter_request
797
                                                         */
798
                                                        $filter_request        = clone $request;
8✔
799
                                                        $counts                = $filters->get_rating_counts( $filter_request );
8✔
800
                                                        $data['rating_counts'] = [];
8✔
801

802
                                                        foreach ( $counts as $key => $value ) {
8✔
803
                                                                $data['rating_counts'][] = (object) [
×
804
                                                                        'rating' => $key,
×
805
                                                                        'count'  => $value,
×
806
                                                                ];
×
807
                                                        }
808
                                                }
809

810
                                                return $data;
8✔
811
                                        },
560✔
812
                                ],
560✔
813
                        ]
560✔
814
                );
560✔
815

816
                // Product queries.
817
                $unsupported_type_enabled = woographql_setting( 'enable_unsupported_product_type', 'off' );
560✔
818

819
                $product_type_keys = array_keys( WooGraphQL::get_enabled_product_types() );
560✔
820
                if ( 'on' === $unsupported_type_enabled ) {
560✔
821
                        $product_type_keys[] = 'unsupported';
2✔
822
                }
823

824
                $product_type_keys = apply_filters( 'woographql_register_product_queries', $product_type_keys );
560✔
825

826
                $product_types = WooGraphQL::get_enabled_product_types();
560✔
827
                if ( 'on' === $unsupported_type_enabled ) {
560✔
828
                        $product_types['unsupported'] = WooGraphQL::get_supported_product_type();
2✔
829
                }
830

831
                foreach ( $product_type_keys as $type_key ) {
560✔
832
                        $field_name = "{$type_key}Product";
560✔
833
                        $type_name  = $product_types[ $type_key ] ?? null;
560✔
834

835
                        if ( empty( $type_name ) ) {
560✔
836
                                continue;
×
837
                        }
838

839
                        register_graphql_field(
560✔
840
                                'RootQuery',
560✔
841
                                $field_name,
560✔
842
                                [
560✔
843
                                        'type'              => $type_name,
560✔
844
                                        /* translators: Product type slug */
845
                                        'description'       => sprintf( __( 'A %s product object', 'wp-graphql-woocommerce' ), $type_key ),
560✔
846
                                        'deprecationReason' => 'Use "product" instead.',
560✔
847
                                        'args'              => [
560✔
848
                                                'id'     => [
560✔
849
                                                        'type'        => 'ID',
560✔
850
                                                        'description' => sprintf(
560✔
851
                                                                /* translators: %s: product type */
852
                                                                __( 'The ID for identifying the %s product', 'wp-graphql-woocommerce' ),
560✔
853
                                                                $type_name
560✔
854
                                                        ),
560✔
855
                                                ],
560✔
856
                                                'idType' => [
560✔
857
                                                        'type'        => 'ProductIdTypeEnum',
560✔
858
                                                        'description' => __( 'Type of ID being used identify product', 'wp-graphql-woocommerce' ),
560✔
859
                                                ],
560✔
860
                                        ],
560✔
861
                                        'resolve'           => static function ( $source, array $args, AppContext $context ) use ( $type_key, $unsupported_type_enabled ) {
560✔
862
                                                $id      = isset( $args['id'] ) ? $args['id'] : null;
2✔
863
                                                $id_type = isset( $args['idType'] ) ? $args['idType'] : 'global_id';
2✔
864

865
                                                $product_id = null;
2✔
866
                                                switch ( $id_type ) {
867
                                                        case 'sku':
2✔
868
                                                                $product_id = \wc_get_product_id_by_sku( $id );
×
869
                                                                break;
×
870
                                                        case 'slug':
2✔
871
                                                                $post       = get_page_by_path( $id, OBJECT, 'product' );
×
872
                                                                $product_id = ! empty( $post ) ? absint( $post->ID ) : 0;
×
873
                                                                break;
×
874
                                                        case 'database_id':
2✔
875
                                                                $product_id = absint( $id );
×
876
                                                                break;
×
877
                                                        case 'global_id':
2✔
878
                                                        default:
879
                                                                $id_components = Relay::fromGlobalId( $id );
2✔
880
                                                                if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
2✔
881
                                                                        throw new UserError( __( 'The "id" is invalid', 'wp-graphql-woocommerce' ) );
×
882
                                                                }
883
                                                                $product_id = absint( $id_components['id'] );
2✔
884
                                                                break;
2✔
885
                                                }
886

887
                                                if ( empty( $product_id ) ) {
2✔
888
                                                        /* translators: %1$s: ID type, %2$s: ID value */
889
                                                        throw new UserError( sprintf( __( 'No product ID was found corresponding to the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $product_id ) );
×
890
                                                }
891

892
                                                if ( \WC()->product_factory->get_product_type( $product_id ) !== $type_key && 'off' === $unsupported_type_enabled ) {
2✔
893
                                                        /* translators: Invalid product type message %1$s: Product ID, %2$s: Product type */
894
                                                        throw new UserError( sprintf( __( 'This product of ID %1$s is not a %2$s product', 'wp-graphql-woocommerce' ), $product_id, $type_key ) );
×
895
                                                }
896

897
                                                $product = get_post( $product_id );
2✔
898
                                                if ( ! is_object( $product ) || 'product' !== $product->post_type ) {
2✔
899
                                                        /* translators: %1$s: ID type, %2$s: ID value */
900
                                                        throw new UserError( sprintf( __( 'No product exists with the %1$s: %2$s', 'wp-graphql-woocommerce' ), $id_type, $product_id ) );
×
901
                                                }
902

903
                                                return Factory::resolve_crud_object( $product_id, $context );
2✔
904
                                        },
560✔
905
                                ]
560✔
906
                        );
560✔
907
                }//end foreach
908
        }
909
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc