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

wp-graphql / wp-graphql-woocommerce / 23774339464

31 Mar 2026 12:23AM UTC coverage: 89.725% (+0.3%) from 89.424%
23774339464

Pull #1003

github

web-flow
Merge bc2a8cf82 into 6fb7b226f
Pull Request #1003: fix: HPOS order mutation data loss, COT cursor pagination, email tests, checkout auth

113 of 121 new or added lines in 9 files covered. (93.39%)

38 existing lines in 3 files now uncovered.

15937 of 17762 relevant lines covered (89.73%)

143.29 hits per line

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

73.55
/includes/data/mutation/class-order-mutation.php
1
<?php
2
/**
3
 * Defines helper functions for executing mutations related to the orders.
4
 *
5
 * @package WPGraphQL\WooCommerce\Data\Mutation
6
 * @since 0.2.0
7
 */
8

9
namespace WPGraphQL\WooCommerce\Data\Mutation;
10

11
use GraphQL\Error\UserError;
12
use WPGraphQL\Utils\Utils;
13

14

15
/**
16
 * Class - Order_Mutation
17
 */
18
class Order_Mutation {
19
        /**
20
         * Filterable authentication function.
21
         *
22
         * @param array                                $input     Input data describing order.
23
         * @param \WPGraphQL\AppContext                $context   AppContext instance.
24
         * @param \GraphQL\Type\Definition\ResolveInfo $info      ResolveInfo instance.
25
         * @param string                               $mutation  Mutation being executed.
26
         * @param integer|null|false                   $order_id  Order ID.
27
         * @throws \GraphQL\Error\UserError  Error locating order.
28
         *
29
         * @return boolean
30
         */
31
        public static function authorized( $input, $context, $info, $mutation = 'create', $order_id = null ) {
32
                /**
33
                 * Get order post type.
34
                 *
35
                 * @var \WP_Post_Type $post_type_object
36
                 */
37
                $post_type_object = get_post_type_object( 'shop_order' );
10✔
38

39
                if ( ! $order_id ) {
10✔
40
                        return apply_filters(
6✔
41
                                "graphql_woocommerce_authorized_to_{$mutation}_orders",
6✔
42
                                current_user_can( $post_type_object->cap->edit_posts ),
6✔
43
                                $order_id,
6✔
44
                                $input,
6✔
45
                                $context,
6✔
46
                                $info
6✔
47
                        );
6✔
48
                }
49

50
                /** @var false|\WC_Order $order */
51
                $order = \wc_get_order( $order_id );
7✔
52
                if ( false === $order ) {
7✔
53
                        throw new UserError(
1✔
54
                                sprintf(
1✔
55
                                        /* translators: %d: Order ID */
56
                                        __( 'Failed to find order with ID of %d.', 'wp-graphql-woocommerce' ),
1✔
57
                                        $order_id
1✔
58
                                )
1✔
59
                        );
1✔
60
                }
61

62
                $post_type = get_post_type( $order_id );
7✔
63
                if ( false === $post_type ) {
7✔
64
                        throw new UserError( __( 'Failed to identify the post type of the order.', 'wp-graphql-woocommerce' ) );
×
65
                }
66

67
                // Return true if user is owner or admin.
68
                $is_owner = 0 !== get_current_user_id() && $order->get_customer_id() === get_current_user_id();
7✔
69
                $is_admin = \wc_rest_check_post_permissions( $post_type, 'edit', $order_id );
7✔
70
                return $is_owner || $is_admin;
7✔
71
        }
72

73
        /**
74
         * Create an order.
75
         *
76
         * @param array                                $input    Input data describing order.
77
         * @param \WPGraphQL\AppContext                $context  AppContext instance.
78
         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
79
         *
80
         * @return integer
81
         *
82
         * @throws \GraphQL\Error\UserError  Error creating order.
83
         */
84
        public static function create_order( $input, $context, $info ) {
85
                $order = new \WC_Order();
6✔
86

87
                $order->set_currency( ! empty( $input['currency'] ) ? $input['currency'] : get_woocommerce_currency() );
6✔
88
                $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
6✔
89
                $order->set_customer_ip_address( \WC_Geolocation::get_ip_address() );
6✔
90
                $order->set_customer_user_agent( wc_get_user_agent() );
6✔
91

92
                $order_id = $order->save();
6✔
93

94
                $order_keys = [
6✔
95
                        'status'       => 'status',
6✔
96
                        'customerId'   => 'customer_id',
6✔
97
                        'customerNote' => 'customer_note',
6✔
98
                        'parent'       => 'parent',
6✔
99
                        'createdVia'   => 'created_via',
6✔
100
                ];
6✔
101

102
                $args = [ 'order_id' => $order_id ];
6✔
103
                foreach ( $input as $key => $value ) {
6✔
104
                        if ( array_key_exists( $key, $order_keys ) ) {
6✔
105
                                $args[ $order_keys[ $key ] ] = $value;
5✔
106
                        }
107
                }
108

109
                /**
110
                 * Action called before order is created.
111
                 *
112
                 * @param array                                $input   Input data describing order.
113
                 * @param \WPGraphQL\AppContext                $context Request AppContext instance.
114
                 * @param \GraphQL\Type\Definition\ResolveInfo $info    Request ResolveInfo instance.
115
                 */
116
                do_action( 'graphql_woocommerce_before_order_create', $input, $context, $info );
6✔
117

118
                $order = \wc_create_order( $args );
6✔
119
                if ( is_wp_error( $order ) ) {
6✔
120
                        throw new UserError( $order->get_error_code() . $order->get_error_message() );
×
121
                }
122

123
                /**
124
                 * Action called after order is created.
125
                 *
126
                 * @param \WC_Order    $order   WC_Order instance.
127
                 * @param array       $input   Input data describing order.
128
                 * @param \WPGraphQL\AppContext  $context Request AppContext instance.
129
                 * @param \GraphQL\Type\Definition\ResolveInfo $info    Request ResolveInfo instance.
130
                 */
131
                do_action( 'graphql_woocommerce_after_order_create', $order, $input, $context, $info );
6✔
132

133
                return $order->get_id();
6✔
134
        }
135

136
        /**
137
         * Add items to order.
138
         *
139
         * @param array                                $input     Input data describing order.
140
         * @param int                                  $order_id  Order object.
141
         * @param \WPGraphQL\AppContext                $context   AppContext instance.
142
         * @param \GraphQL\Type\Definition\ResolveInfo $info      ResolveInfo instance.
143
         *
144
         * @throws \Exception  Failed to retrieve order.
145
         *
146
         * @return void
147
         */
148
        public static function add_items( $input, $order_id, $context, $info ) {
149
                /** @var \WC_Order|false $order */
UNCOV
150
                $order = \WC_Order_Factory::get_order( $order_id );
×
UNCOV
151
                if ( false === $order ) {
×
152
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
153
                }
154

UNCOV
155
                $item_group_keys = [
×
UNCOV
156
                        'lineItems'     => 'line_item',
×
UNCOV
157
                        'shippingLines' => 'shipping',
×
UNCOV
158
                        'feeLines'      => 'fee',
×
UNCOV
159
                ];
×
160

UNCOV
161
                $order_items = [];
×
UNCOV
162
                foreach ( $input as $key => $group_items ) {
×
UNCOV
163
                        if ( array_key_exists( $key, $item_group_keys ) ) {
×
UNCOV
164
                                $type                 = $item_group_keys[ $key ];
×
UNCOV
165
                                $order_items[ $type ] = [];
×
166

167
                                /**
168
                                 * Action called before an item group is added to an order.
169
                                 *
170
                                 * @param array                                $group_items  Items data being added.
171
                                 * @param \WC_Order                            $order        Order object.
172
                                 * @param \WPGraphQL\AppContext                $context      Request AppContext instance.
173
                                 * @param \GraphQL\Type\Definition\ResolveInfo $info         Request ResolveInfo instance.
174
                                 */
UNCOV
175
                                do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $group_items, $order, $context, $info );
×
176

UNCOV
177
                                foreach ( $group_items as $item_data ) {
×
UNCOV
178
                                        $item = self::set_item(
×
UNCOV
179
                                                $item_data,
×
UNCOV
180
                                                $type,
×
UNCOV
181
                                                $order,
×
UNCOV
182
                                                $context,
×
UNCOV
183
                                                $info
×
UNCOV
184
                                        );
×
185

186
                                        /**
187
                                         * Action called before an item group is added to an order.
188
                                         *
189
                                         * @param \WC_Order_Item                       $item      Order item object.
190
                                         * @param array                                $item_data Item data being added.
191
                                         * @param \WC_Order                            $order     Order object.
192
                                         * @param \WPGraphQL\AppContext                $context   Request AppContext instance.
193
                                         * @param \GraphQL\Type\Definition\ResolveInfo $info      Request ResolveInfo instance.
194
                                         */
UNCOV
195
                                        do_action( "graphql_woocommerce_before_{$type}_added_to_order", $item, $item_data, $order, $context, $info );
×
196

UNCOV
197
                                        if ( 0 === $item->get_id() ) {
×
UNCOV
198
                                                $order->add_item( $item );
×
UNCOV
199
                                                $order_items[ $type ][] = $item;
×
200
                                        } else {
UNCOV
201
                                                $item->save();
×
UNCOV
202
                                                $order_items[ $type ][] = $item;
×
203
                                        }
204
                                }
205

206
                                /**
207
                                 * Action called after an item group is added to an order, and before the order has been saved with the new items.
208
                                 *
209
                                 * @param array                                $group_items  Item data being added.
210
                                 * @param \WC_Order                            $order        Order object.
211
                                 * @param \WPGraphQL\AppContext                $context      Request AppContext instance.
212
                                 * @param \GraphQL\Type\Definition\ResolveInfo $info         Request ResolveInfo instance.
213
                                 */
UNCOV
214
                                do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $group_items, $order, $context, $info );
×
215
                        }//end if
216
                }//end foreach
217

218
                /**
219
                 * Action called after all items have been added and right before the new items have been saved.
220
                 *
221
                 * @param array<string, array<\WC_Order_Item>> $order_items Order items.
222
                 * @param \WC_Order                            $order       WC_Order instance.
223
                 * @param array                                $input       Input data describing order.
224
                 * @param \WPGraphQL\AppContext                $context     Request AppContext instance.
225
                 * @param \GraphQL\Type\Definition\ResolveInfo $info        Request ResolveInfo instance.
226
                 */
UNCOV
227
                do_action( 'graphql_woocommerce_before_new_order_items_save', $order_items, $order, $input, $context, $info );
×
228

UNCOV
229
                $order->save();
×
230
        }
231

232
        /**
233
         *
234
         * @param array<string, mixed>                 $item_data  Item data.
235
         * @param string                               $type       Item type.
236
         * @param \WC_Order                            $order      Order object.
237
         * @param \WPGraphQL\AppContext                $context    AppContext instance.
238
         * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
239
         *
240
         * @return \WC_Order_Item
241
         */
242
        public static function set_item( $item_data, $type, $order, $context, $info ) {
243
                $item_id    = ! empty( $item_data['id'] ) ? $item_data['id'] : 0;
6✔
244
                $item_class = self::get_order_item_classname( $type, $item_id );
6✔
245

246
                /** @var \WC_Order_Item $item */
247
                $item = new $item_class( $item_id );
6✔
248

249
                /**
250
                 * Filter the order item object before it is created.
251
                 *
252
                 * @param \WC_Order_Item                       $item       Order item object.
253
                 * @param array                                $item_data  Item data.
254
                 * @param \WC_Order                            $order      Order object.
255
                 * @param \WPGraphQL\AppContext                $context    AppContext instance.
256
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
257
                 */
258
                $item = apply_filters( "graphql_create_order_{$type}_object", $item, $item_data, $order, $context, $info );
6✔
259

260
                self::map_input_to_item( $item, $item_data, $type );
6✔
261

262
                /**
263
                 * Action called after an order item is created.
264
                 *
265
                 * @param \WC_Order_Item                       $item       Order item object.
266
                 * @param array                                $item_data  Item data.
267
                 * @param \WC_Order                            $order      Order object.
268
                 * @param \WPGraphQL\AppContext                $context    AppContext instance.
269
                 * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
270
                 */
271
                do_action( "graphql_create_order_{$type}", $item, $item_data, $order, $context, $info );
6✔
272

273
                return $item;
6✔
274
        }
275

276
        /**
277
         * Get order item class name.
278
         *
279
         * @param string $type Order item type.
280
         * @param int    $id  Order item ID.
281
         *
282
         * @return string
283
         */
284
        public static function get_order_item_classname( $type, $id = 0 ) {
285
                $classname = false;
6✔
286
                switch ( $type ) {
287
                        case 'line_item':
6✔
288
                        case 'product':
4✔
289
                                $classname = 'WC_Order_Item_Product';
6✔
290
                                break;
6✔
291
                        case 'coupon':
4✔
292
                                $classname = 'WC_Order_Item_Coupon';
×
293
                                break;
×
294
                        case 'fee':
4✔
295
                                $classname = 'WC_Order_Item_Fee';
4✔
296
                                break;
4✔
297
                        case 'shipping':
4✔
298
                                $classname = 'WC_Order_Item_Shipping';
4✔
299
                                break;
4✔
300
                        case 'tax':
×
301
                                $classname = 'WC_Order_Item_Tax';
×
302
                                break;
×
303
                }
304

305
                $classname = apply_filters( 'woocommerce_get_order_item_classname', $classname, $type, $id ); // phpcs:ignore WordPress.NamingConventions
6✔
306

307
                return $classname;
6✔
308
        }
309

310
        /**
311
         * Return array of item mapped with the provided $item_keys and extracts $meta_data
312
         *
313
         * @param \WC_Order_Item &$item      Order item.
314
         * @param array          $input      Item input data.
315
         * @param string         $type       Item type.
316
         *
317
         * @throws \Exception Failed to retrieve connected product.
318
         *
319
         * @return void
320
         */
321
        protected static function map_input_to_item( &$item, $input, $type ) {
322
                $item_keys = self::get_order_item_keys( $type );
6✔
323

324
                $args      = [];
6✔
325
                $meta_data = null;
6✔
326
                foreach ( $input as $key => $value ) {
6✔
327
                        if ( array_key_exists( $key, $item_keys ) ) {
6✔
328
                                $args[ $item_keys[ $key ] ] = $value;
6✔
329
                        } elseif ( 'metaData' === $key ) {
5✔
330
                                $meta_data = $value;
4✔
331
                        } else {
332
                                $args[ $key ] = $value;
5✔
333
                        }
334
                }
335

336
                // Auto-fill line item name, subtotal, and total from the product when not provided.
337
                $has_product_id   = ! empty( $args['product_id'] ) || ! empty( $args['variation_id'] );
6✔
338
                $missing_defaults = ! isset( $args['subtotal'] ) || ! isset( $args['total'] ) || ! isset( $args['name'] );
6✔
339
                if ( 'line_item' === $type && $has_product_id && $missing_defaults ) {
6✔
340
                        $product_id = self::get_product_id( $args );
6✔
341
                        $product    = ! empty( $product_id ) ? wc_get_product( $product_id ) : null;
6✔
342
                        if ( ! is_object( $product ) ) {
6✔
343
                                throw new \Exception( __( 'Failed to retrieve product connected to order item.', 'wp-graphql-woocommerce' ) );
×
344
                        }
345

346
                        $total            = wc_get_price_excluding_tax( $product, [ 'qty' => $args['quantity'] ?? 1 ] );
6✔
347
                        $args['subtotal'] = $args['subtotal'] ?? $total;
6✔
348
                        $args['total']    = $args['total'] ?? $total;
6✔
349
                        $args['name']     = $args['name'] ?? $product->get_name();
6✔
350
                }
351

352
                // Set item props.
353
                foreach ( $args as $key => $value ) {
6✔
354
                        if ( is_callable( [ $item, "set_{$key}" ] ) ) {
6✔
355
                                $item->{"set_{$key}"}( $value );
6✔
356
                        }
357
                }
358

359
                // Update item meta data if any is found.
360
                if ( empty( $meta_data ) ) {
6✔
361
                        return;
6✔
362
                }
363

364
                foreach ( $meta_data as $entry ) {
4✔
365
                        $exists = $item->get_meta( $entry['key'], true, 'edit' );
4✔
366
                        if ( '' !== $exists && $exists !== $entry['value'] ) {
4✔
367
                                $item->update_meta_data( $entry['key'], $entry['value'] );
1✔
368
                        } else {
369
                                $item->add_meta_data( $entry['key'], $entry['value'] );
4✔
370
                        }
371
                }
372
        }
373

374
        /**
375
         * Returns array of item keys by item type.
376
         *
377
         * @param string $type  Order item type.
378
         *
379
         * @return array
380
         */
381
        protected static function get_order_item_keys( $type ) {
382
                switch ( $type ) {
383
                        case 'line_item':
6✔
384
                                return [
6✔
385
                                        'productId'   => 'product_id',
6✔
386
                                        'variationId' => 'variation_id',
6✔
387
                                        'taxClass'    => 'tax_class',
6✔
388
                                ];
6✔
389

390
                        case 'shipping':
4✔
391
                                return [
4✔
392
                                        'name'        => 'order_item_name',
4✔
393
                                        'methodTitle' => 'method_title',
4✔
394
                                        'methodId'    => 'method_id',
4✔
395
                                        'instanceId'  => 'instance_id',
4✔
396
                                ];
4✔
397

398
                        case 'fee':
4✔
399
                                return [
4✔
400
                                        'name'      => 'name',
4✔
401
                                        'taxClass'  => 'tax_class',
4✔
402
                                        'taxStatus' => 'tax_status',
4✔
403
                                ];
4✔
404
                        default:
405
                                /**
406
                                 * Allow filtering of order item keys for unknown item types.
407
                                 *
408
                                 * @param array  $item_keys  Order item keys.
409
                                 * @param string $type       Order item type slug.
410
                                 */
411
                                return apply_filters( 'woographql_get_order_item_keys', [], $type );
×
412
                }//end switch
413
        }
414

415
        /**
416
         * Gets the product ID from the SKU or line item data ID.
417
         *
418
         * @param array $data  Line item data.
419
         *
420
         * @return integer
421
         * @throws \GraphQL\Error\UserError When SKU or ID is not valid.
422
         */
423
        protected static function get_product_id( $data ) {
424
                if ( ! empty( $data['sku'] ) ) {
6✔
425
                        $product_id = (int) wc_get_product_id_by_sku( $data['sku'] );
×
426
                } elseif ( ! empty( $data['variation_id'] ) ) {
6✔
427
                        $product_id = (int) $data['variation_id'];
4✔
428
                } elseif ( ! empty( $data['product_id'] ) ) {
6✔
429
                        $product_id = (int) $data['product_id'];
6✔
430
                } else {
431
                        throw new UserError( __( 'Product ID or SKU is required.', 'wp-graphql-woocommerce' ) );
×
432
                }
433

434
                return $product_id;
6✔
435
        }
436

437
        /**
438
         * Create/Update order item meta data.
439
         *
440
         * @param int                                  $item_id    Order item ID.
441
         * @param array                                $meta_data  Array of meta data.
442
         * @param \WPGraphQL\AppContext                $context    AppContext instance.
443
         * @param \GraphQL\Type\Definition\ResolveInfo $info       ResolveInfo instance.
444
         *
445
         * @throws \GraphQL\Error\UserError|\Exception  Invalid item input | Failed to retrieve order item.
446
         *
447
         * @return void
448
         */
449
        protected static function update_item_meta_data( $item_id, $meta_data, $context, $info ) {
450
                $item = \WC_Order_Factory::get_order_item( $item_id );
×
451
                if ( ! is_object( $item ) ) {
×
452
                        throw new \Exception( __( 'Failed to retrieve order item.', 'wp-graphql-woocommerce' ) );
×
453
                }
454

455
                foreach ( $meta_data as $entry ) {
×
456
                        $exists = $item->get_meta( $entry['key'], true, 'edit' );
×
457
                        if ( '' !== $exists && $exists !== $entry['value'] ) {
×
458
                                \wc_update_order_item_meta( $item_id, $entry['key'], $entry['value'] );
×
459
                        } else {
460
                                \wc_add_order_item_meta( $item_id, $entry['key'], $entry['value'] );
×
461
                        }
462
                }
463
        }
464

465
        /**
466
         * Add meta data not set in self::create_order().
467
         *
468
         * @param int                                  $order_id  Order ID.
469
         * @param array                                $input     Order properties.
470
         * @param \WPGraphQL\AppContext                $context   AppContext instance.
471
         * @param \GraphQL\Type\Definition\ResolveInfo $info      ResolveInfo instance.
472
         *
473
         * @throws \Exception  Failed to retrieve order.
474
         *
475
         * @return void
476
         */
477
        public static function add_order_meta( $order_id, $input, $context, $info ) {
UNCOV
478
                $order = \WC_Order_Factory::get_order( $order_id );
×
UNCOV
479
                if ( ! is_object( $order ) ) {
×
480
                        throw new \Exception( __( 'Failed to retrieve order.', 'wp-graphql-woocommerce' ) );
×
481
                }
482

NEW
483
                self::prepare_order( $order, $input, $context, $info );
×
484
        }
485

486
        /**
487
         * Sets all order props, address fields, items, and meta on the provided order object
488
         * and saves once, mirroring the WC REST API pattern to avoid HPOS data loss from
489
         * multiple save() calls across different object instances.
490
         *
491
         * @param \WC_Order                            $order   WC_Order instance.
492
         * @param array                                $input   Input data describing order.
493
         * @param \WPGraphQL\AppContext                $context AppContext instance.
494
         * @param \GraphQL\Type\Definition\ResolveInfo $info    ResolveInfo instance.
495
         *
496
         * @return void
497
         */
498
        public static function prepare_order( $order, $input, $context, $info ) {
499

500
                foreach ( $input as $key => $value ) {
7✔
501
                        switch ( $key ) {
502
                                case 'clientMutationId':
7✔
503
                                case 'id':
7✔
504
                                case 'orderId':
7✔
505
                                case 'coupons':
7✔
506
                                case 'status':
7✔
507
                                case 'isPaid':
7✔
508
                                        break;
7✔
509
                                case 'billing':
7✔
510
                                case 'shipping':
7✔
511
                                        $formatted_address = Customer_Mutation::address_input_mapping( $value, $key );
4✔
512
                                        foreach ( $formatted_address as $field => $field_value ) {
4✔
513
                                                if ( is_callable( [ $order, "set_{$key}_{$field}" ] ) ) {
4✔
514
                                                        $order->{"set_{$key}_{$field}"}( $field_value );
4✔
515
                                                }
516
                                        }
517
                                        break;
4✔
518
                                case 'lineItems':
7✔
519
                                case 'shippingLines':
7✔
520
                                case 'feeLines':
7✔
521
                                        $item_group_keys = [
6✔
522
                                                'lineItems'     => 'line_item',
6✔
523
                                                'shippingLines' => 'shipping',
6✔
524
                                                'feeLines'      => 'fee',
6✔
525
                                        ];
6✔
526
                                        $type            = $item_group_keys[ $key ];
6✔
527

528
                                        /**
529
                                         * Action called before an item group is added to an order.
530
                                         *
531
                                         * @param array                                $value    Items data being added.
532
                                         * @param \WC_Order                            $order    Order object.
533
                                         * @param \WPGraphQL\AppContext                $context  Request AppContext instance.
534
                                         * @param \GraphQL\Type\Definition\ResolveInfo $info     Request ResolveInfo instance.
535
                                         */
536
                                        do_action( "graphql_woocommerce_before_{$type}s_added_to_order", $value, $order, $context, $info );
6✔
537

538
                                        foreach ( $value as $item_data ) {
6✔
539
                                                $item = self::set_item( $item_data, $type, $order, $context, $info );
6✔
540

541
                                                /**
542
                                                 * Action called before an item is added to an order.
543
                                                 *
544
                                                 * @param \WC_Order_Item                       $item      Order item object.
545
                                                 * @param array                                $item_data Item data being added.
546
                                                 * @param \WC_Order                            $order     Order object.
547
                                                 * @param \WPGraphQL\AppContext                $context   Request AppContext instance.
548
                                                 * @param \GraphQL\Type\Definition\ResolveInfo $info      Request ResolveInfo instance.
549
                                                 */
550
                                                do_action( "graphql_woocommerce_before_{$type}_added_to_order", $item, $item_data, $order, $context, $info );
6✔
551

552
                                                if ( 0 === $item->get_id() ) {
6✔
553
                                                        $order->add_item( $item );
6✔
554
                                                } else {
555
                                                        $item->save();
1✔
556
                                                }
557
                                        }
558

559
                                        /**
560
                                         * Action called after an item group is added to an order.
561
                                         *
562
                                         * @param array                                $value    Item data being added.
563
                                         * @param \WC_Order                            $order    Order object.
564
                                         * @param \WPGraphQL\AppContext                $context  Request AppContext instance.
565
                                         * @param \GraphQL\Type\Definition\ResolveInfo $info     Request ResolveInfo instance.
566
                                         */
567
                                        do_action( "graphql_woocommerce_after_{$type}s_added_to_order", $value, $order, $context, $info );
6✔
568
                                        break;
6✔
569
                                case 'metaData':
7✔
570
                                        if ( is_array( $value ) ) {
5✔
571
                                                foreach ( $value as $meta ) {
5✔
572
                                                        $order->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
5✔
573
                                                }
574
                                        }
575
                                        break;
5✔
576
                                default:
577
                                        $prop = \wc_graphql_camel_case_to_underscore( $key );
6✔
578
                                        if ( is_callable( [ $order, "set_{$prop}" ] ) ) {
6✔
579
                                                $order->{"set_{$prop}"}( $value );
6✔
580
                                        }
581
                                        break;
6✔
582
                        }//end switch
583
                }//end foreach
584

585
                /**
586
                 * Action called before changes to order meta are saved.
587
                 *
588
                 * @param \WC_Order                            $order   WC_Order instance.
589
                 * @param array                                $input   Order props array.
590
                 * @param \WPGraphQL\AppContext                $context Request AppContext instance.
591
                 * @param \GraphQL\Type\Definition\ResolveInfo $info    Request ResolveInfo instance.
592
                 */
593
                do_action( 'graphql_woocommerce_before_order_meta_save', $order, $input, $context, $info );
7✔
594

595
                $order->save_meta_data();
7✔
596
                $order->save();
7✔
597
        }
598

599
        /**
600
         * Update address.
601
         *
602
         * @param array     $address   Address data.
603
         * @param \WC_Order $order  WC_Order instance.
604
         * @param string    $type      Address type.
605
         *
606
         * @throws \Exception  Failed to retrieve order.
607
         *
608
         * @return void
609
         */
610
        protected static function update_address( $address, $order, $type = 'billing' ) {
UNCOV
611
                $formatted_address = Customer_Mutation::address_input_mapping( $address, $type );
×
UNCOV
612
                foreach ( $formatted_address as $key => $value ) {
×
UNCOV
613
                        if ( is_callable( [ $order, "set_{$type}_{$key}" ] ) ) {
×
UNCOV
614
                                $order->{"set_{$type}_{$key}"}( $value );
×
615
                        }
NEW
616
                        $order->apply_changes();
×
617
                }
618
        }
619

620
        /**
621
         * Applies coupons to WC_Order instance.
622
         *
623
         * @param \WC_Order $order   WC_Order instance.
624
         * @param array     $coupons Coupon codes to be applied to order.
625
         *
626
         * @return void
627
         */
628
        public static function apply_coupons( $order, $coupons ) {
629
                // Remove all coupons first to ensure calculation is correct.
630
                foreach ( $order->get_items( 'coupon' ) as $coupon ) {
4✔
631
                        /**
632
                         * Order item coupon.
633
                         *
634
                         * @var \WC_Order_Item_Coupon $coupon
635
                         */
636

637
                        $order->remove_coupon( $coupon->get_code() );
1✔
638
                }
639

640
                foreach ( $coupons as $code ) {
4✔
641
                        $results = $order->apply_coupon( sanitize_text_field( $code ) );
4✔
642
                        if ( is_wp_error( $results ) ) {
4✔
643
                                do_action( 'graphql_woocommerce_' . $results->get_error_code(), $results, $code, $coupons, $order );
×
644
                        }
645
                }
646

647
                $order->save();
4✔
648
        }
649

650
        /**
651
         * Validates order customer
652
         *
653
         * @param string $customer_id  ID of customer for order.
654
         *
655
         * @return bool
656
         */
657
        public static function validate_customer( $customer_id ) {
658
                $id = Utils::get_database_id_from_id( $customer_id );
4✔
659
                if ( ! $id ) {
4✔
660
                        return false;
×
661
                }
662

663
                if ( false === get_user_by( 'id', $id ) ) {
4✔
664
                        return false;
×
665
                }
666

667
                // Make sure customer is part of blog.
668
                if ( is_multisite() && ! is_user_member_of_blog( $id ) ) {
4✔
669
                        add_user_to_blog( get_current_blog_id(), $id, 'customer' );
×
670
                }
671

672
                return true;
4✔
673
        }
674

675
        /**
676
         * Purge object when creating.
677
         *
678
         * @param null|\WC_Order|\WPGraphQL\WooCommerce\Model\Order $order         Object data.
679
         * @param boolean                                           $force_delete  Delete or put in trash.
680
         *
681
         * @return bool
682
         * @throws \GraphQL\Error\UserError  Failed to delete order.
683
         */
684
        public static function purge( $order, $force_delete = true ) {
685
                if ( ! empty( $order ) ) {
1✔
686
                        return $order->delete( $force_delete );
1✔
687
                }
688

689
                return false;
×
690
        }
691
}
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