1010
1111use GraphQL \Error \UserError ;
1212
13+
1314/**
1415 * Class - Order_Mutation
1516 */
@@ -86,8 +87,8 @@ public static function create_order( $input, $context, $info ) {
8687 /**
8788 * Action called before order is created.
8889 *
89- * @param array $input Input data describing order.
90- * @param \WPGraphQL\AppContext $context Request AppContext instance.
90+ * @param array $input Input data describing order.
91+ * @param \WPGraphQL\AppContext $context Request AppContext instance.
9192 * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
9293 */
9394 do_action ( 'graphql_woocommerce_before_order_create ' , $ input , $ context , $ info );
@@ -118,78 +119,186 @@ public static function create_order( $input, $context, $info ) {
118119 * @param \WPGraphQL\AppContext $context AppContext instance.
119120 * @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
120121 *
122+ * @throws \Exception Failed to retrieve order.
123+ *
121124 * @return void
122125 */
123126 public static function add_items ( $ input , $ order_id , $ context , $ info ) {
127+ /** @var \WC_Order|false $order */
128+ $ order = \WC_Order_Factory::get_order ( $ order_id );
129+ if ( false === $ order ) {
130+ throw new \Exception ( __ ( 'Failed to retrieve order. ' , 'wp-graphql-woocommerce ' ) );
131+ }
132+
124133 $ item_group_keys = [
125134 'lineItems ' => 'line_item ' ,
126135 'shippingLines ' => 'shipping ' ,
127136 'feeLines ' => 'fee ' ,
128137 ];
129138
130- $ item_groups = [];
131- foreach ( $ input as $ key => $ items ) {
139+ $ order_items = [];
140+ foreach ( $ input as $ key => $ group_items ) {
132141 if ( array_key_exists ( $ key , $ item_group_keys ) ) {
133- $ type = $ item_group_keys [ $ key ];
142+ $ type = $ item_group_keys [ $ key ];
143+ $ order_items [ $ type ] = [];
134144
135145 /**
136146 * Action called before an item group is added to an order.
137147 *
138- * @param array $items Item data being added.
139- * @param integer $order_id ID of target order.
140- * @param \WPGraphQL\AppContext $context Request AppContext instance.
141- * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
148+ * @param array $group_items Items data being added.
149+ * @param \WC_Order $ order Order object .
150+ * @param \WPGraphQL\AppContext $context Request AppContext instance.
151+ * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
142152 */
143- do_action ( "graphql_woocommerce_before_ {$ type }s_added_to_order " , $ items , $ order_id , $ context , $ info );
144-
145- foreach ( $ items as $ item_data ) {
146- // Create Order item.
147- $ item_id = ( ! empty ( $ item_data ['id ' ] ) && \WC_Order_Factory::get_order_item ( $ item_data ['id ' ] ) )
148- ? $ item_data ['id ' ]
149- : \wc_add_order_item ( $ order_id , [ 'order_item_type ' => $ type ] );
150-
151- // Continue if order item creation failed.
152- if ( ! $ item_id ) {
153- continue ;
153+ do_action ( "graphql_woocommerce_before_ {$ type }s_added_to_order " , $ group_items , $ order , $ context , $ info );
154+
155+ foreach ( $ group_items as $ item_data ) {
156+ $ item = self ::set_item (
157+ $ item_data ,
158+ $ type ,
159+ $ order ,
160+ $ context ,
161+ $ info
162+ );
163+
164+ /**
165+ * Action called before an item group is added to an order.
166+ *
167+ * @param \WC_Order_Item $item Order item object.
168+ * @param array $item_data Item data being added.
169+ * @param \WC_Order $order Order object.
170+ * @param \WPGraphQL\AppContext $context Request AppContext instance.
171+ * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
172+ */
173+ do_action ( "graphql_woocommerce_before_ {$ type }_added_to_order " , $ item , $ item_data , $ order , $ context , $ info );
174+
175+ if ( 0 === $ item ->get_id () ) {
176+ $ order ->add_item ( $ item );
177+ $ order_items [ $ type ][] = $ item ;
178+ } else {
179+ $ item ->save ();
180+ $ order_items [ $ type ][] = $ item ;
154181 }
155-
156- // Add input item data to order item.
157- $ item_keys = self ::get_order_item_keys ( $ type );
158- self ::map_input_to_item ( $ item_id , $ item_data , $ item_keys , $ context , $ info );
159182 }
160183
161184 /**
162- * Action called after an item group is added to an order.
185+ * Action called after an item group is added to an order, and before the order has been saved with the new items .
163186 *
164- * @param array $items Item data being added.
165- * @param integer $order_id ID of target order.
166- * @param \WPGraphQL\AppContext $context Request AppContext instance.
167- * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
187+ * @param array $group_items Item data being added.
188+ * @param \WC_Order $ order Order object .
189+ * @param \WPGraphQL\AppContext $context Request AppContext instance.
190+ * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
168191 */
169- do_action ( "graphql_woocommerce_after_ {$ type }s_added_to_order " , $ items , $ order_id , $ context , $ info );
192+ do_action ( "graphql_woocommerce_after_ {$ type }s_added_to_order " , $ group_items , $ order , $ context , $ info );
170193 }//end if
171194 }//end foreach
195+
196+ /**
197+ * Action called after all items have been added and right before the new items have been saved.
198+ *
199+ * @param array<string, array<\WC_Order_Item>> $order_items Order items.
200+ * @param \WC_Order $order WC_Order instance.
201+ * @param array $input Input data describing order.
202+ * @param \WPGraphQL\AppContext $context Request AppContext instance.
203+ * @param \GraphQL\Type\Definition\ResolveInfo $info Request ResolveInfo instance.
204+ */
205+ do_action ( 'graphql_woocommerce_before_new_order_items_save ' , $ order_items , $ order , $ input , $ context , $ info );
206+
207+ $ order ->save ();
172208 }
173209
174210 /**
175- * Return array of item mapped with the provided $item_keys and extracts $meta_data
176211 *
177- * @param integer $item_id Order item ID .
178- * @param array $input Item input data .
179- * @param array $item_keys Item key map .
212+ * @param array<string, mixed> $item_data Item data .
213+ * @param string $type Item type .
214+ * @param \WC_Order $order Order object .
180215 * @param \WPGraphQL\AppContext $context AppContext instance.
181216 * @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
182217 *
183- * @throws \Exception Failed to retrieve order item | Failed to retrieve connected product.
218+ * @return \WC_Order_Item
219+ */
220+ public static function set_item ( $ item_data , $ type , $ order , $ context , $ info ) {
221+ $ item_id = ! empty ( $ item_data ['id ' ] ) ? $ item_data ['id ' ] : 0 ;
222+ $ item_class = self ::get_order_item_classname ( $ type , $ item_id );
223+
224+ /** @var \WC_Order_Item $item */
225+ $ item = new $ item_class ( $ item_id );
226+
227+ /**
228+ * Filter the order item object before it is created.
229+ *
230+ * @param \WC_Order_Item $item Order item object.
231+ * @param array $item_data Item data.
232+ * @param \WC_Order $order Order object.
233+ * @param \WPGraphQL\AppContext $context AppContext instance.
234+ * @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
235+ */
236+ $ item = apply_filters ( "graphql_create_order_ {$ type }_object " , $ item , $ item_data , $ order , $ context , $ info );
237+
238+ self ::map_input_to_item ( $ item , $ item_data , $ type );
239+
240+ /**
241+ * Action called after an order item is created.
242+ *
243+ * @param \WC_Order_Item $item Order item object.
244+ * @param array $item_data Item data.
245+ * @param \WC_Order $order Order object.
246+ * @param \WPGraphQL\AppContext $context AppContext instance.
247+ * @param \GraphQL\Type\Definition\ResolveInfo $info ResolveInfo instance.
248+ */
249+ do_action ( "graphql_create_order_ {$ type }" , $ item , $ item_data , $ order , $ context , $ info );
250+
251+ return $ item ;
252+ }
253+
254+ /**
255+ * Get order item class name.
184256 *
185- * @return int
257+ * @param string $type Order item type.
258+ * @param int $id Order item ID.
259+ *
260+ * @return string
186261 */
187- protected static function map_input_to_item ( $ item_id , $ input , $ item_keys , $ context , $ info ) {
188- $ order_item = \WC_Order_Factory::get_order_item ( $ item_id );
189- if ( ! is_object ( $ order_item ) ) {
190- throw new \Exception ( __ ( 'Failed to retrieve order item. ' , 'wp-graphql-woocommerce ' ) );
262+ public static function get_order_item_classname ( $ type , $ id = 0 ) {
263+ $ classname = false ;
264+ switch ( $ type ) {
265+ case 'line_item ' :
266+ case 'product ' :
267+ $ classname = 'WC_Order_Item_Product ' ;
268+ break ;
269+ case 'coupon ' :
270+ $ classname = 'WC_Order_Item_Coupon ' ;
271+ break ;
272+ case 'fee ' :
273+ $ classname = 'WC_Order_Item_Fee ' ;
274+ break ;
275+ case 'shipping ' :
276+ $ classname = 'WC_Order_Item_Shipping ' ;
277+ break ;
278+ case 'tax ' :
279+ $ classname = 'WC_Order_Item_Tax ' ;
280+ break ;
191281 }
192282
283+ $ classname = apply_filters ( 'woocommerce_get_order_item_classname ' , $ classname , $ type , $ id ); // phpcs:ignore WordPress.NamingConventions
284+
285+ return $ classname ;
286+ }
287+
288+ /**
289+ * Return array of item mapped with the provided $item_keys and extracts $meta_data
290+ *
291+ * @param \WC_Order_Item &$item Order item.
292+ * @param array $input Item input data.
293+ * @param string $type Item type.
294+ *
295+ * @throws \Exception Failed to retrieve connected product.
296+ *
297+ * @return void
298+ */
299+ protected static function map_input_to_item ( &$ item , $ input , $ type ) {
300+ $ item_keys = self ::get_order_item_keys ( $ type );
301+
193302 $ args = [];
194303 $ meta_data = null ;
195304 foreach ( $ input as $ key => $ value ) {
@@ -203,10 +312,9 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont
203312 }
204313
205314 // Calculate to subtotal/total for line items.
206-
207315 if ( isset ( $ args ['quantity ' ] ) ) {
208- $ product = ( ! empty ( $ order_item ['product_id ' ] ) )
209- ? wc_get_product ( $ order_item ['product_id ' ] )
316+ $ product = ( ! empty ( $ item ['product_id ' ] ) )
317+ ? wc_get_product ( $ item ['product_id ' ] )
210318 : wc_get_product ( self ::get_product_id ( $ args ) );
211319 if ( ! is_object ( $ product ) ) {
212320 throw new \Exception ( __ ( 'Failed to retrieve product connected to order item. ' , 'wp-graphql-woocommerce ' ) );
@@ -219,18 +327,24 @@ protected static function map_input_to_item( $item_id, $input, $item_keys, $cont
219327
220328 // Set item props.
221329 foreach ( $ args as $ key => $ value ) {
222- if ( is_callable ( [ $ order_item , "set_ {$ key }" ] ) ) {
223- $ order_item ->{"set_ {$ key }" }( $ value );
330+ if ( is_callable ( [ $ item , "set_ {$ key }" ] ) ) {
331+ $ item ->{"set_ {$ key }" }( $ value );
224332 }
225333 }
226334
227335 // Update item meta data if any is found.
228- if ( 0 !== $ item_id && ! empty ( $ meta_data ) ) {
229- // Update item meta data.
230- self ::update_item_meta_data ( $ item_id , $ meta_data , $ context , $ info );
336+ if ( empty ( $ meta_data ) ) {
337+ return ;
231338 }
232339
233- return $ order_item ->save ();
340+ foreach ( $ meta_data as $ entry ) {
341+ $ exists = $ item ->get_meta ( $ entry ['key ' ], true , 'edit ' );
342+ if ( '' !== $ exists && $ exists !== $ entry ['value ' ] ) {
343+ $ item ->update_meta_data ( $ entry ['key ' ], $ entry ['value ' ] );
344+ } else {
345+ $ item ->add_meta_data ( $ entry ['key ' ], $ entry ['value ' ] );
346+ }
347+ }
234348 }
235349
236350 /**
@@ -285,10 +399,10 @@ protected static function get_order_item_keys( $type ) {
285399 protected static function get_product_id ( $ data ) {
286400 if ( ! empty ( $ data ['sku ' ] ) ) {
287401 $ product_id = (int ) wc_get_product_id_by_sku ( $ data ['sku ' ] );
288- } elseif ( ! empty ( $ data ['product_id ' ] ) && empty ( $ data ['variation_id ' ] ) ) {
289- $ product_id = (int ) $ data ['product_id ' ];
290402 } elseif ( ! empty ( $ data ['variation_id ' ] ) ) {
291403 $ product_id = (int ) $ data ['variation_id ' ];
404+ } elseif ( ! empty ( $ data ['product_id ' ] ) ) {
405+ $ product_id = (int ) $ data ['product_id ' ];
292406 } else {
293407 throw new UserError ( __ ( 'Product ID or SKU is required. ' , 'wp-graphql-woocommerce ' ) );
294408 }
0 commit comments