Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like WC_API_Orders often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WC_API_Orders, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class WC_API_Orders extends WC_API_Resource { |
||
18 | |||
19 | /** @var string $base the route base */ |
||
20 | protected $base = '/orders'; |
||
21 | |||
22 | /** @var string $post_type the custom post type */ |
||
23 | protected $post_type = 'shop_order'; |
||
24 | |||
25 | /** |
||
26 | * Register the routes for this class |
||
27 | * |
||
28 | * GET|POST /orders |
||
29 | * GET /orders/count |
||
30 | * GET|PUT|DELETE /orders/<id> |
||
31 | * GET /orders/<id>/notes |
||
32 | * |
||
33 | * @since 2.1 |
||
34 | * @param array $routes |
||
35 | * @return array |
||
36 | */ |
||
37 | public function register_routes( $routes ) { |
||
38 | |||
39 | # GET|POST /orders |
||
40 | $routes[ $this->base ] = array( |
||
41 | array( array( $this, 'get_orders' ), WC_API_Server::READABLE ), |
||
42 | array( array( $this, 'create_order' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ), |
||
43 | ); |
||
44 | |||
45 | # GET /orders/count |
||
46 | $routes[ $this->base . '/count' ] = array( |
||
47 | array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ), |
||
48 | ); |
||
49 | |||
50 | # GET /orders/statuses |
||
51 | $routes[ $this->base . '/statuses' ] = array( |
||
52 | array( array( $this, 'get_order_statuses' ), WC_API_Server::READABLE ), |
||
53 | ); |
||
54 | |||
55 | # GET|PUT|DELETE /orders/<id> |
||
56 | $routes[ $this->base . '/(?P<id>\d+)' ] = array( |
||
57 | array( array( $this, 'get_order' ), WC_API_Server::READABLE ), |
||
58 | array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ), |
||
59 | array( array( $this, 'delete_order' ), WC_API_Server::DELETABLE ), |
||
60 | ); |
||
61 | |||
62 | # GET|POST /orders/<id>/notes |
||
63 | $routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array( |
||
64 | array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ), |
||
65 | array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ), |
||
66 | ); |
||
67 | |||
68 | # GET|PUT|DELETE /orders/<order_id>/notes/<id> |
||
69 | $routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array( |
||
70 | array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ), |
||
71 | array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ), |
||
72 | array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ), |
||
73 | ); |
||
74 | |||
75 | # GET|POST /orders/<order_id>/refunds |
||
76 | $routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array( |
||
77 | array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ), |
||
78 | array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ), |
||
79 | ); |
||
80 | |||
81 | # GET|PUT|DELETE /orders/<order_id>/refunds/<id> |
||
82 | $routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array( |
||
83 | array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ), |
||
84 | array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ), |
||
85 | array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ), |
||
86 | ); |
||
87 | |||
88 | # POST|PUT /orders/bulk |
||
89 | $routes[ $this->base . '/bulk' ] = array( |
||
90 | array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ), |
||
91 | ); |
||
92 | |||
93 | return $routes; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * Get all orders |
||
98 | * |
||
99 | * @since 2.1 |
||
100 | * @param string $fields |
||
101 | * @param array $filter |
||
102 | * @param string $status |
||
103 | * @param int $page |
||
104 | * @return array |
||
105 | */ |
||
106 | View Code Duplication | public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) { |
|
107 | |||
108 | if ( ! empty( $status ) ) { |
||
109 | $filter['status'] = $status; |
||
110 | } |
||
111 | |||
112 | $filter['page'] = $page; |
||
113 | |||
114 | $query = $this->query_orders( $filter ); |
||
115 | |||
116 | $orders = array(); |
||
117 | |||
118 | foreach ( $query->posts as $order_id ) { |
||
119 | |||
120 | if ( ! $this->is_readable( $order_id ) ) { |
||
121 | continue; |
||
122 | } |
||
123 | |||
124 | $orders[] = current( $this->get_order( $order_id, $fields, $filter ) ); |
||
125 | } |
||
126 | |||
127 | $this->server->add_pagination_headers( $query ); |
||
128 | |||
129 | return array( 'orders' => $orders ); |
||
130 | } |
||
131 | |||
132 | |||
133 | /** |
||
134 | * Get the order for the given ID. |
||
135 | * |
||
136 | * @since 2.1 |
||
137 | * @param int $id The order ID. |
||
138 | * @param array $fields Request fields. |
||
139 | * @param array $filter Request filters. |
||
140 | * @return array |
||
141 | */ |
||
142 | public function get_order( $id, $fields = null, $filter = array() ) { |
||
143 | |||
144 | // Ensure order ID is valid & user has permission to read. |
||
145 | $id = $this->validate_request( $id, $this->post_type, 'read' ); |
||
146 | |||
147 | if ( is_wp_error( $id ) ) { |
||
148 | return $id; |
||
149 | } |
||
150 | |||
151 | // Get the decimal precession. |
||
152 | $dp = ( isset( $filter['dp'] ) ? intval( $filter['dp'] ) : 2 ); |
||
153 | $order = wc_get_order( $id ); |
||
154 | $order_post = get_post( $id ); |
||
155 | $expand = array(); |
||
156 | |||
157 | if ( ! empty( $filter['expand'] ) ) { |
||
158 | $expand = explode( ',', $filter['expand'] ); |
||
159 | } |
||
160 | |||
161 | $order_data = array( |
||
162 | 'id' => $order->id, |
||
163 | 'order_number' => $order->get_order_number(), |
||
164 | 'order_key' => $order->order_key, |
||
165 | 'created_at' => $this->server->format_datetime( $order_post->post_date_gmt ), |
||
166 | 'updated_at' => $this->server->format_datetime( $order_post->post_modified_gmt ), |
||
167 | 'completed_at' => $this->server->format_datetime( $order->completed_date, true ), |
||
168 | 'status' => $order->get_status(), |
||
169 | 'currency' => $order->get_order_currency(), |
||
170 | 'total' => wc_format_decimal( $order->get_total(), $dp ), |
||
171 | 'subtotal' => wc_format_decimal( $order->get_subtotal(), $dp ), |
||
172 | 'total_line_items_quantity' => $order->get_item_count(), |
||
173 | 'total_tax' => wc_format_decimal( $order->get_total_tax(), $dp ), |
||
174 | 'total_shipping' => wc_format_decimal( $order->get_total_shipping(), $dp ), |
||
175 | 'cart_tax' => wc_format_decimal( $order->get_cart_tax(), $dp ), |
||
176 | 'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), $dp ), |
||
177 | 'total_discount' => wc_format_decimal( $order->get_total_discount(), $dp ), |
||
178 | 'shipping_methods' => $order->get_shipping_method(), |
||
179 | 'payment_details' => array( |
||
180 | 'method_id' => $order->payment_method, |
||
181 | 'method_title' => $order->payment_method_title, |
||
182 | 'paid' => isset( $order->paid_date ), |
||
183 | ), |
||
184 | 'billing_address' => array( |
||
185 | 'first_name' => $order->billing_first_name, |
||
186 | 'last_name' => $order->billing_last_name, |
||
187 | 'company' => $order->billing_company, |
||
188 | 'address_1' => $order->billing_address_1, |
||
189 | 'address_2' => $order->billing_address_2, |
||
190 | 'city' => $order->billing_city, |
||
191 | 'state' => $order->billing_state, |
||
192 | 'postcode' => $order->billing_postcode, |
||
193 | 'country' => $order->billing_country, |
||
194 | 'email' => $order->billing_email, |
||
195 | 'phone' => $order->billing_phone, |
||
196 | ), |
||
197 | 'shipping_address' => array( |
||
198 | 'first_name' => $order->shipping_first_name, |
||
199 | 'last_name' => $order->shipping_last_name, |
||
200 | 'company' => $order->shipping_company, |
||
201 | 'address_1' => $order->shipping_address_1, |
||
202 | 'address_2' => $order->shipping_address_2, |
||
203 | 'city' => $order->shipping_city, |
||
204 | 'state' => $order->shipping_state, |
||
205 | 'postcode' => $order->shipping_postcode, |
||
206 | 'country' => $order->shipping_country, |
||
207 | ), |
||
208 | 'note' => $order->customer_note, |
||
209 | 'customer_ip' => $order->customer_ip_address, |
||
210 | 'customer_user_agent' => $order->customer_user_agent, |
||
211 | 'customer_id' => $order->get_user_id(), |
||
212 | 'view_order_url' => $order->get_view_order_url(), |
||
213 | 'line_items' => array(), |
||
214 | 'shipping_lines' => array(), |
||
215 | 'tax_lines' => array(), |
||
216 | 'fee_lines' => array(), |
||
217 | 'coupon_lines' => array(), |
||
218 | 'is_vat_exempt' => $order->is_vat_exempt === 'yes' ? true : false, |
||
219 | ); |
||
220 | |||
221 | // Add line items. |
||
222 | foreach ( $order->get_items() as $item_id => $item ) { |
||
223 | $product = $order->get_product_from_item( $item ); |
||
224 | $product_id = null; |
||
225 | $product_sku = null; |
||
226 | |||
227 | // Check if the product exists. |
||
228 | View Code Duplication | if ( is_object( $product ) ) { |
|
229 | $product_id = ( isset( $product->variation_id ) ) ? $product->variation_id : $product->id; |
||
230 | $product_sku = $product->get_sku(); |
||
231 | } |
||
232 | |||
233 | $meta = new WC_Order_Item_Meta( $item, $product ); |
||
234 | |||
235 | $item_meta = array(); |
||
236 | |||
237 | $hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_'; |
||
238 | |||
239 | View Code Duplication | foreach ( $meta->get_formatted( $hideprefix ) as $meta_key => $formatted_meta ) { |
|
240 | $item_meta[] = array( |
||
241 | 'key' => $formatted_meta['key'], |
||
242 | 'label' => $formatted_meta['label'], |
||
243 | 'value' => $formatted_meta['value'], |
||
244 | ); |
||
245 | } |
||
246 | |||
247 | $line_item = array( |
||
248 | 'id' => $item_id, |
||
249 | 'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $dp ), |
||
250 | 'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $dp ), |
||
251 | 'total' => wc_format_decimal( $order->get_line_total( $item, false, false ), $dp ), |
||
252 | 'total_tax' => wc_format_decimal( $item['line_tax'], $dp ), |
||
253 | 'price' => wc_format_decimal( $order->get_item_total( $item, false, false ), $dp ), |
||
254 | 'quantity' => wc_stock_amount( $item['qty'] ), |
||
255 | 'tax_class' => ( ! empty( $item['tax_class'] ) ) ? $item['tax_class'] : null, |
||
256 | 'name' => $item['name'], |
||
257 | 'product_id' => $product_id, |
||
258 | 'sku' => $product_sku, |
||
259 | 'meta' => $item_meta, |
||
260 | ); |
||
261 | |||
262 | View Code Duplication | if ( in_array( 'products', $expand ) ) { |
|
263 | $_product_data = WC()->api->WC_API_Products->get_product( $product_id ); |
||
264 | |||
265 | if ( isset( $_product_data['product'] ) ) { |
||
266 | $line_item['product_data'] = $_product_data['product']; |
||
267 | } |
||
268 | } |
||
269 | |||
270 | $order_data['line_items'][] = $line_item; |
||
271 | } |
||
272 | |||
273 | // Add shipping. |
||
274 | View Code Duplication | foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) { |
|
275 | $order_data['shipping_lines'][] = array( |
||
276 | 'id' => $shipping_item_id, |
||
277 | 'method_id' => $shipping_item['method_id'], |
||
278 | 'method_title' => $shipping_item['name'], |
||
279 | 'total' => wc_format_decimal( $shipping_item['cost'], $dp ), |
||
280 | ); |
||
281 | } |
||
282 | |||
283 | // Add taxes. |
||
284 | foreach ( $order->get_tax_totals() as $tax_code => $tax ) { |
||
285 | $tax_line = array( |
||
286 | 'id' => $tax->id, |
||
287 | 'rate_id' => $tax->rate_id, |
||
288 | 'code' => $tax_code, |
||
289 | 'title' => $tax->label, |
||
290 | 'total' => wc_format_decimal( $tax->amount, $dp ), |
||
291 | 'compound' => (bool) $tax->is_compound, |
||
292 | ); |
||
293 | |||
294 | if ( in_array( 'taxes', $expand ) ) { |
||
295 | $_rate_data = WC()->api->WC_API_Taxes->get_tax( $tax->rate_id ); |
||
296 | |||
297 | if ( isset( $_rate_data['tax'] ) ) { |
||
298 | $tax_line['rate_data'] = $_rate_data['tax']; |
||
299 | } |
||
300 | } |
||
301 | |||
302 | $order_data['tax_lines'][] = $tax_line; |
||
303 | } |
||
304 | |||
305 | // Add fees. |
||
306 | View Code Duplication | foreach ( $order->get_fees() as $fee_item_id => $fee_item ) { |
|
307 | $order_data['fee_lines'][] = array( |
||
308 | 'id' => $fee_item_id, |
||
309 | 'title' => $fee_item['name'], |
||
310 | 'tax_class' => ( ! empty( $fee_item['tax_class'] ) ) ? $fee_item['tax_class'] : null, |
||
311 | 'total' => wc_format_decimal( $order->get_line_total( $fee_item ), $dp ), |
||
312 | 'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), $dp ), |
||
313 | ); |
||
314 | } |
||
315 | |||
316 | // Add coupons. |
||
317 | foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) { |
||
318 | $coupon_line = array( |
||
319 | 'id' => $coupon_item_id, |
||
320 | 'code' => $coupon_item['name'], |
||
321 | 'amount' => wc_format_decimal( $coupon_item['discount_amount'], $dp ), |
||
322 | ); |
||
323 | |||
324 | View Code Duplication | if ( in_array( 'coupons', $expand ) ) { |
|
325 | $_coupon_data = WC()->api->WC_API_Coupons->get_coupon_by_code( $coupon_item['name'] ); |
||
326 | |||
327 | if ( isset( $_coupon_data['coupon'] ) ) { |
||
328 | $coupon_line['coupon_data'] = $_coupon_data['coupon']; |
||
329 | } |
||
330 | } |
||
331 | |||
332 | $order_data['coupon_lines'][] = $coupon_line; |
||
333 | } |
||
334 | |||
335 | return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) ); |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Get the total number of orders |
||
340 | * |
||
341 | * @since 2.4 |
||
342 | * @param string $status |
||
343 | * @param array $filter |
||
344 | * @return array |
||
345 | */ |
||
346 | public function get_orders_count( $status = null, $filter = array() ) { |
||
347 | |||
348 | try { |
||
349 | if ( ! current_user_can( 'read_private_shop_orders' ) ) { |
||
350 | throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), 401 ); |
||
351 | } |
||
352 | |||
353 | if ( ! empty( $status ) ) { |
||
354 | |||
355 | if ( $status == 'any' ) { |
||
356 | |||
357 | $order_statuses = array(); |
||
358 | |||
359 | foreach ( wc_get_order_statuses() as $slug => $name ) { |
||
360 | $filter['status'] = str_replace( 'wc-', '', $slug ); |
||
361 | $query = $this->query_orders( $filter ); |
||
362 | $order_statuses[ str_replace( 'wc-', '', $slug ) ] = (int) $query->found_posts; |
||
363 | } |
||
364 | |||
365 | return array( 'count' => $order_statuses ); |
||
366 | |||
367 | } else { |
||
368 | $filter['status'] = $status; |
||
369 | } |
||
370 | } |
||
371 | |||
372 | $query = $this->query_orders( $filter ); |
||
373 | |||
374 | return array( 'count' => (int) $query->found_posts ); |
||
375 | |||
376 | } catch ( WC_API_Exception $e ) { |
||
377 | return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); |
||
378 | } |
||
379 | } |
||
380 | |||
381 | /** |
||
382 | * Get a list of valid order statuses |
||
383 | * |
||
384 | * Note this requires no specific permissions other than being an authenticated |
||
385 | * API user. Order statuses (particularly custom statuses) could be considered |
||
386 | * private information which is why it's not in the API index. |
||
387 | * |
||
388 | * @since 2.1 |
||
389 | * @return array |
||
390 | */ |
||
391 | public function get_order_statuses() { |
||
392 | |||
393 | $order_statuses = array(); |
||
394 | |||
395 | foreach ( wc_get_order_statuses() as $slug => $name ) { |
||
396 | $order_statuses[ str_replace( 'wc-', '', $slug ) ] = $name; |
||
397 | } |
||
398 | |||
399 | return array( 'order_statuses' => apply_filters( 'woocommerce_api_order_statuses_response', $order_statuses, $this ) ); |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Create an order |
||
404 | * |
||
405 | * @since 2.2 |
||
406 | * @param array $data raw order data |
||
407 | * @return array |
||
408 | */ |
||
409 | public function create_order( $data ) { |
||
410 | global $wpdb; |
||
411 | |||
412 | wc_transaction_query( 'start' ); |
||
413 | |||
414 | try { |
||
415 | View Code Duplication | if ( ! isset( $data['order'] ) ) { |
|
416 | throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 ); |
||
417 | } |
||
418 | |||
419 | $data = $data['order']; |
||
420 | |||
421 | // permission check |
||
422 | if ( ! current_user_can( 'publish_shop_orders' ) ) { |
||
423 | throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order', __( 'You do not have permission to create orders', 'woocommerce' ), 401 ); |
||
424 | } |
||
425 | |||
426 | $data = apply_filters( 'woocommerce_api_create_order_data', $data, $this ); |
||
427 | |||
428 | // default order args, note that status is checked for validity in wc_create_order() |
||
429 | $default_order_args = array( |
||
430 | 'status' => isset( $data['status'] ) ? $data['status'] : '', |
||
431 | 'customer_note' => isset( $data['note'] ) ? $data['note'] : null, |
||
432 | ); |
||
433 | |||
434 | // if creating order for existing customer |
||
435 | if ( ! empty( $data['customer_id'] ) ) { |
||
436 | |||
437 | // make sure customer exists |
||
438 | if ( false === get_user_by( 'id', $data['customer_id'] ) ) { |
||
439 | throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid', 'woocommerce' ), 400 ); |
||
440 | } |
||
441 | |||
442 | $default_order_args['customer_id'] = $data['customer_id']; |
||
443 | } |
||
444 | |||
445 | // create the pending order |
||
446 | $order = $this->create_base_order( $default_order_args, $data ); |
||
447 | |||
448 | View Code Duplication | if ( is_wp_error( $order ) ) { |
|
449 | throw new WC_API_Exception( 'woocommerce_api_cannot_create_order', sprintf( __( 'Cannot create order: %s', 'woocommerce' ), implode( ', ', $order->get_error_messages() ) ), 400 ); |
||
450 | } |
||
451 | |||
452 | // billing/shipping addresses |
||
453 | $this->set_order_addresses( $order, $data ); |
||
454 | |||
455 | $lines = array( |
||
456 | 'line_item' => 'line_items', |
||
457 | 'shipping' => 'shipping_lines', |
||
458 | 'fee' => 'fee_lines', |
||
459 | 'coupon' => 'coupon_lines', |
||
460 | ); |
||
461 | |||
462 | View Code Duplication | foreach ( $lines as $line_type => $line ) { |
|
463 | |||
464 | if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { |
||
465 | |||
466 | $set_item = "set_{$line_type}"; |
||
467 | |||
468 | foreach ( $data[ $line ] as $item ) { |
||
469 | |||
470 | $this->$set_item( $order, $item, 'create' ); |
||
471 | } |
||
472 | } |
||
473 | } |
||
474 | |||
475 | // set is vat exempt |
||
476 | if ( isset( $data['is_vat_exempt'] ) ) { |
||
477 | update_post_meta( $order->id, '_is_vat_exempt', $data['is_vat_exempt'] ? 'yes' : 'no' ); |
||
478 | } |
||
479 | |||
480 | // calculate totals and set them |
||
481 | $order->calculate_totals(); |
||
482 | |||
483 | // payment method (and payment_complete() if `paid` == true) |
||
484 | View Code Duplication | if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { |
|
485 | |||
486 | // method ID & title are required |
||
487 | if ( empty( $data['payment_details']['method_id'] ) || empty( $data['payment_details']['method_title'] ) ) { |
||
488 | throw new WC_API_Exception( 'woocommerce_invalid_payment_details', __( 'Payment method ID and title are required', 'woocommerce' ), 400 ); |
||
489 | } |
||
490 | |||
491 | update_post_meta( $order->id, '_payment_method', $data['payment_details']['method_id'] ); |
||
492 | update_post_meta( $order->id, '_payment_method_title', $data['payment_details']['method_title'] ); |
||
493 | |||
494 | // mark as paid if set |
||
495 | if ( isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { |
||
496 | $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); |
||
497 | } |
||
498 | } |
||
499 | |||
500 | // set order currency |
||
501 | View Code Duplication | if ( isset( $data['currency'] ) ) { |
|
502 | |||
503 | if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { |
||
504 | throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid', 'woocommerce'), 400 ); |
||
505 | } |
||
506 | |||
507 | update_post_meta( $order->id, '_order_currency', $data['currency'] ); |
||
508 | } |
||
509 | |||
510 | // set order meta |
||
511 | View Code Duplication | if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { |
|
512 | $this->set_order_meta( $order->id, $data['order_meta'] ); |
||
513 | } |
||
514 | |||
515 | // HTTP 201 Created |
||
516 | $this->server->send_status( 201 ); |
||
517 | |||
518 | wc_delete_shop_order_transients( $order->id ); |
||
519 | |||
520 | do_action( 'woocommerce_api_create_order', $order->id, $data, $this ); |
||
521 | |||
522 | wc_transaction_query( 'commit' ); |
||
523 | |||
524 | return $this->get_order( $order->id ); |
||
525 | |||
526 | } catch ( WC_API_Exception $e ) { |
||
527 | |||
528 | wc_transaction_query( 'rollback' ); |
||
529 | |||
530 | return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); |
||
531 | } |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * Creates new WC_Order. |
||
536 | * |
||
537 | * Requires a separate function for classes that extend WC_API_Orders. |
||
538 | * |
||
539 | * @since 2.3 |
||
540 | * @param $args array |
||
541 | * @return WC_Order |
||
542 | */ |
||
543 | protected function create_base_order( $args, $data ) { |
||
546 | |||
547 | /** |
||
548 | * Edit an order |
||
549 | * |
||
550 | * @since 2.2 |
||
551 | * @param int $id the order ID |
||
552 | * @param array $data |
||
553 | * @return array |
||
554 | */ |
||
555 | public function edit_order( $id, $data ) { |
||
556 | try { |
||
557 | View Code Duplication | if ( ! isset( $data['order'] ) ) { |
|
558 | throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order' ), 400 ); |
||
559 | } |
||
560 | |||
561 | $data = $data['order']; |
||
562 | |||
563 | $update_totals = false; |
||
564 | |||
565 | $id = $this->validate_request( $id, $this->post_type, 'edit' ); |
||
566 | |||
567 | if ( is_wp_error( $id ) ) { |
||
568 | return $id; |
||
569 | } |
||
570 | |||
571 | $data = apply_filters( 'woocommerce_api_edit_order_data', $data, $id, $this ); |
||
572 | $order = wc_get_order( $id ); |
||
573 | |||
574 | if ( empty( $order ) ) { |
||
575 | throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 ); |
||
576 | } |
||
577 | |||
578 | $order_args = array( 'order_id' => $order->id ); |
||
579 | |||
580 | // Customer note. |
||
581 | if ( isset( $data['note'] ) ) { |
||
582 | $order_args['customer_note'] = $data['note']; |
||
583 | } |
||
584 | |||
585 | // Customer ID. |
||
586 | View Code Duplication | if ( isset( $data['customer_id'] ) && $data['customer_id'] != $order->get_user_id() ) { |
|
587 | // Make sure customer exists. |
||
588 | if ( false === get_user_by( 'id', $data['customer_id'] ) ) { |
||
589 | throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid', 'woocommerce' ), 400 ); |
||
590 | } |
||
591 | |||
592 | update_post_meta( $order->id, '_customer_user', $data['customer_id'] ); |
||
593 | } |
||
594 | |||
595 | // Billing/shipping address. |
||
596 | $this->set_order_addresses( $order, $data ); |
||
597 | |||
598 | $lines = array( |
||
599 | 'line_item' => 'line_items', |
||
600 | 'shipping' => 'shipping_lines', |
||
601 | 'fee' => 'fee_lines', |
||
602 | 'coupon' => 'coupon_lines', |
||
603 | ); |
||
604 | |||
605 | View Code Duplication | foreach ( $lines as $line_type => $line ) { |
|
606 | |||
607 | if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) { |
||
608 | |||
609 | $update_totals = true; |
||
610 | |||
611 | foreach ( $data[ $line ] as $item ) { |
||
612 | |||
613 | // Item ID is always required. |
||
614 | if ( ! array_key_exists( 'id', $item ) ) { |
||
615 | throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID is required', 'woocommerce' ), 400 ); |
||
616 | } |
||
617 | |||
618 | // Create item. |
||
619 | if ( is_null( $item['id'] ) ) { |
||
620 | $this->set_item( $order, $line_type, $item, 'create' ); |
||
621 | } elseif ( $this->item_is_null( $item ) ) { |
||
622 | // Delete item. |
||
623 | wc_delete_order_item( $item['id'] ); |
||
624 | } else { |
||
625 | // Update item. |
||
626 | $this->set_item( $order, $line_type, $item, 'update' ); |
||
627 | } |
||
628 | } |
||
629 | } |
||
630 | } |
||
631 | |||
632 | // Payment method (and payment_complete() if `paid` == true and order needs payment). |
||
633 | if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) { |
||
634 | |||
635 | // Method ID. |
||
636 | if ( isset( $data['payment_details']['method_id'] ) ) { |
||
637 | update_post_meta( $order->id, '_payment_method', $data['payment_details']['method_id'] ); |
||
638 | } |
||
639 | |||
640 | // Method title. |
||
641 | if ( isset( $data['payment_details']['method_title'] ) ) { |
||
642 | update_post_meta( $order->id, '_payment_method_title', $data['payment_details']['method_title'] ); |
||
643 | } |
||
644 | |||
645 | // Mark as paid if set. |
||
646 | View Code Duplication | if ( $order->needs_payment() && isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) { |
|
647 | $order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' ); |
||
648 | } |
||
649 | } |
||
650 | |||
651 | // Set order currency. |
||
652 | View Code Duplication | if ( isset( $data['currency'] ) ) { |
|
653 | if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) { |
||
654 | throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid', 'woocommerce' ), 400 ); |
||
655 | } |
||
656 | |||
657 | update_post_meta( $order->id, '_order_currency', $data['currency'] ); |
||
658 | } |
||
659 | |||
660 | // If items have changed, recalculate order totals. |
||
661 | if ( $update_totals ) { |
||
662 | $order->calculate_totals(); |
||
663 | } |
||
664 | |||
665 | // Update order meta. |
||
666 | View Code Duplication | if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) { |
|
667 | $this->set_order_meta( $order->id, $data['order_meta'] ); |
||
668 | } |
||
669 | |||
670 | // Update the order post to set customer note/modified date. |
||
671 | wc_update_order( $order_args ); |
||
672 | |||
673 | // Order status. |
||
674 | View Code Duplication | if ( ! empty( $data['status'] ) ) { |
|
675 | $order->update_status( $data['status'], isset( $data['status_note'] ) ? $data['status_note'] : '' ); |
||
676 | } |
||
677 | |||
678 | wc_delete_shop_order_transients( $order->id ); |
||
679 | |||
680 | do_action( 'woocommerce_api_edit_order', $order->id, $data, $this ); |
||
681 | |||
682 | return $this->get_order( $id ); |
||
683 | } catch ( WC_API_Exception $e ) { |
||
684 | return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); |
||
685 | } |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Delete an order |
||
690 | * |
||
691 | * @param int $id the order ID |
||
692 | * @param bool $force true to permanently delete order, false to move to trash |
||
693 | * @return array |
||
694 | */ |
||
695 | View Code Duplication | public function delete_order( $id, $force = false ) { |
|
696 | |||
697 | $id = $this->validate_request( $id, $this->post_type, 'delete' ); |
||
698 | |||
699 | if ( is_wp_error( $id ) ) { |
||
700 | return $id; |
||
701 | } |
||
702 | |||
703 | wc_delete_shop_order_transients( $id ); |
||
704 | |||
705 | do_action( 'woocommerce_api_delete_order', $id, $this ); |
||
706 | |||
707 | return $this->delete( $id, 'order', ( 'true' === $force ) ); |
||
708 | } |
||
709 | |||
710 | /** |
||
711 | * Helper method to get order post objects |
||
712 | * |
||
713 | * @since 2.1 |
||
714 | * @param array $args request arguments for filtering query |
||
715 | * @return WP_Query |
||
716 | */ |
||
717 | protected function query_orders( $args ) { |
||
718 | |||
719 | // set base query arguments |
||
720 | $query_args = array( |
||
721 | 'fields' => 'ids', |
||
722 | 'post_type' => $this->post_type, |
||
723 | 'post_status' => array_keys( wc_get_order_statuses() ) |
||
724 | ); |
||
725 | |||
726 | // add status argument |
||
727 | View Code Duplication | if ( ! empty( $args['status'] ) ) { |
|
728 | $statuses = 'wc-' . str_replace( ',', ',wc-', $args['status'] ); |
||
729 | $statuses = explode( ',', $statuses ); |
||
730 | $query_args['post_status'] = $statuses; |
||
731 | |||
732 | unset( $args['status'] ); |
||
733 | } |
||
734 | |||
735 | if ( ! empty( $args['customer_id'] ) ) { |
||
736 | $query_args['meta_query'] = array( |
||
737 | array( |
||
738 | 'key' => '_customer_user', |
||
739 | 'value' => absint( $args['customer_id'] ), |
||
740 | 'compare' => '=' |
||
741 | ) |
||
742 | ); |
||
743 | } |
||
744 | |||
745 | $query_args = $this->merge_query_args( $query_args, $args ); |
||
746 | |||
747 | return new WP_Query( $query_args ); |
||
748 | } |
||
749 | |||
750 | /** |
||
751 | * Helper method to set/update the billing & shipping addresses for |
||
752 | * an order |
||
753 | * |
||
754 | * @since 2.1 |
||
755 | * @param \WC_Order $order |
||
756 | * @param array $data |
||
757 | */ |
||
758 | View Code Duplication | protected function set_order_addresses( $order, $data ) { |
|
759 | |||
760 | $address_fields = array( |
||
761 | 'first_name', |
||
762 | 'last_name', |
||
763 | 'company', |
||
764 | 'email', |
||
765 | 'phone', |
||
766 | 'address_1', |
||
767 | 'address_2', |
||
768 | 'city', |
||
769 | 'state', |
||
770 | 'postcode', |
||
771 | 'country', |
||
772 | ); |
||
773 | |||
774 | $billing_address = $shipping_address = array(); |
||
775 | |||
776 | // billing address |
||
777 | if ( isset( $data['billing_address'] ) && is_array( $data['billing_address'] ) ) { |
||
778 | |||
779 | foreach ( $address_fields as $field ) { |
||
780 | |||
781 | if ( isset( $data['billing_address'][ $field ] ) ) { |
||
782 | $billing_address[ $field ] = wc_clean( $data['billing_address'][ $field ] ); |
||
783 | } |
||
784 | } |
||
785 | |||
786 | unset( $address_fields['email'] ); |
||
787 | unset( $address_fields['phone'] ); |
||
788 | } |
||
789 | |||
790 | // shipping address |
||
791 | if ( isset( $data['shipping_address'] ) && is_array( $data['shipping_address'] ) ) { |
||
792 | |||
793 | foreach ( $address_fields as $field ) { |
||
794 | |||
795 | if ( isset( $data['shipping_address'][ $field ] ) ) { |
||
796 | $shipping_address[ $field ] = wc_clean( $data['shipping_address'][ $field ] ); |
||
797 | } |
||
798 | } |
||
799 | } |
||
800 | |||
801 | $order->set_address( $billing_address, 'billing' ); |
||
802 | $order->set_address( $shipping_address, 'shipping' ); |
||
803 | |||
804 | // update user meta |
||
805 | if ( $order->get_user_id() ) { |
||
806 | foreach ( $billing_address as $key => $value ) { |
||
807 | update_user_meta( $order->get_user_id(), 'billing_' . $key, $value ); |
||
808 | } |
||
809 | foreach ( $shipping_address as $key => $value ) { |
||
810 | update_user_meta( $order->get_user_id(), 'shipping_' . $key, $value ); |
||
811 | } |
||
812 | } |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * Helper method to add/update order meta, with two restrictions: |
||
817 | * |
||
818 | * 1) Only non-protected meta (no leading underscore) can be set |
||
819 | * 2) Meta values must be scalar (int, string, bool) |
||
820 | * |
||
821 | * @since 2.2 |
||
822 | * @param int $order_id valid order ID |
||
823 | * @param array $order_meta order meta in array( 'meta_key' => 'meta_value' ) format |
||
824 | */ |
||
825 | View Code Duplication | protected function set_order_meta( $order_id, $order_meta ) { |
|
826 | |||
827 | foreach ( $order_meta as $meta_key => $meta_value ) { |
||
828 | |||
829 | if ( is_string( $meta_key) && ! is_protected_meta( $meta_key ) && is_scalar( $meta_value ) ) { |
||
830 | update_post_meta( $order_id, $meta_key, $meta_value ); |
||
831 | } |
||
832 | } |
||
833 | } |
||
834 | |||
835 | /** |
||
836 | * Helper method to check if the resource ID associated with the provided item is null |
||
837 | * |
||
838 | * Items can be deleted by setting the resource ID to null |
||
839 | * |
||
840 | * @since 2.2 |
||
841 | * @param array $item item provided in the request body |
||
842 | * @return bool true if the item resource ID is null, false otherwise |
||
843 | */ |
||
844 | protected function item_is_null( $item ) { |
||
856 | |||
857 | /** |
||
858 | * Wrapper method to create/update order items |
||
859 | * |
||
860 | * When updating, the item ID provided is checked to ensure it is associated |
||
861 | * with the order. |
||
862 | * |
||
863 | * @since 2.2 |
||
864 | * @param \WC_Order $order order |
||
865 | * @param string $item_type |
||
866 | * @param array $item item provided in the request body |
||
867 | * @param string $action either 'create' or 'update' |
||
868 | * @throws WC_API_Exception if item ID is not associated with order |
||
869 | */ |
||
870 | View Code Duplication | protected function set_item( $order, $item_type, $item, $action ) { |
|
871 | global $wpdb; |
||
872 | |||
873 | $set_method = "set_{$item_type}"; |
||
874 | |||
875 | // verify provided line item ID is associated with order |
||
876 | if ( 'update' === $action ) { |
||
877 | |||
878 | $result = $wpdb->get_row( |
||
879 | $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d", |
||
880 | absint( $item['id'] ), |
||
881 | absint( $order->id ) |
||
882 | ) ); |
||
883 | |||
884 | if ( is_null( $result ) ) { |
||
885 | throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID provided is not associated with order', 'woocommerce' ), 400 ); |
||
886 | } |
||
887 | } |
||
888 | |||
889 | $this->$set_method( $order, $item, $action ); |
||
890 | } |
||
891 | |||
892 | /** |
||
893 | * Create or update a line item |
||
894 | * |
||
895 | * @since 2.2 |
||
896 | * @param \WC_Order $order |
||
897 | * @param array $item line item data |
||
898 | * @param string $action 'create' to add line item or 'update' to update it |
||
899 | * @throws WC_API_Exception invalid data, server error |
||
900 | */ |
||
901 | protected function set_line_item( $order, $item, $action ) { |
||
999 | |||
1000 | /** |
||
1001 | * Given a product ID & API provided variations, find the correct variation ID to use for calculation |
||
1002 | * We can't just trust input from the API to pass a variation_id manually, otherwise you could pass |
||
1003 | * the cheapest variation ID but provide other information so we have to look up the variation ID. |
||
1004 | * |
||
1005 | * @param WC_Product $product Product instance |
||
1006 | * @return int Returns an ID if a valid variation was found for this product |
||
1007 | */ |
||
1008 | View Code Duplication | public function get_variation_id( $product, $variations = array() ) { |
|
1038 | |||
1039 | /** |
||
1040 | * Utility function to see if the meta array contains data from variations |
||
1041 | */ |
||
1042 | View Code Duplication | protected function array_contains( $needles, $haystack ) { |
|
1050 | |||
1051 | /** |
||
1052 | * Create or update an order shipping method |
||
1053 | * |
||
1054 | * @since 2.2 |
||
1055 | * @param \WC_Order $order |
||
1056 | * @param array $shipping item data |
||
1057 | * @param string $action 'create' to add shipping or 'update' to update it |
||
1058 | * @throws WC_API_Exception invalid data, server error |
||
1059 | */ |
||
1060 | protected function set_shipping( $order, $shipping, $action ) { |
||
1105 | |||
1106 | /** |
||
1107 | * Create or update an order fee |
||
1108 | * |
||
1109 | * @since 2.2 |
||
1110 | * @param \WC_Order $order |
||
1111 | * @param array $fee item data |
||
1112 | * @param string $action 'create' to add fee or 'update' to update it |
||
1113 | * @throws WC_API_Exception invalid data, server error |
||
1114 | */ |
||
1115 | protected function set_fee( $order, $fee, $action ) { |
||
1186 | |||
1187 | /** |
||
1188 | * Create or update an order coupon |
||
1189 | * |
||
1190 | * @since 2.2 |
||
1191 | * @param \WC_Order $order |
||
1192 | * @param array $coupon item data |
||
1193 | * @param string $action 'create' to add coupon or 'update' to update it |
||
1194 | * @throws WC_API_Exception invalid data, server error |
||
1195 | */ |
||
1196 | protected function set_coupon( $order, $coupon, $action ) { |
||
1235 | |||
1236 | /** |
||
1237 | * Get the admin order notes for an order |
||
1238 | * |
||
1239 | * @since 2.1 |
||
1240 | * @param string $order_id order ID |
||
1241 | * @param string|null $fields fields to include in response |
||
1242 | * @return array |
||
1243 | */ |
||
1244 | public function get_order_notes( $order_id, $fields = null ) { |
||
1274 | |||
1275 | /** |
||
1276 | * Get an order note for the given order ID and ID |
||
1277 | * |
||
1278 | * @since 2.2 |
||
1279 | * @param string $order_id order ID |
||
1280 | * @param string $id order note ID |
||
1281 | * @param string|null $fields fields to limit response to |
||
1282 | * @return array |
||
1283 | */ |
||
1284 | public function get_order_note( $order_id, $id, $fields = null ) { |
||
1317 | |||
1318 | /** |
||
1319 | * Create a new order note for the given order |
||
1320 | * |
||
1321 | * @since 2.2 |
||
1322 | * @param string $order_id order ID |
||
1323 | * @param array $data raw request data |
||
1324 | * @return WP_Error|array error or created note response data |
||
1325 | */ |
||
1326 | public function create_order_note( $order_id, $data ) { |
||
1373 | |||
1374 | /** |
||
1375 | * Edit the order note |
||
1376 | * |
||
1377 | * @since 2.2 |
||
1378 | * @param string $order_id order ID |
||
1379 | * @param string $id note ID |
||
1380 | * @param array $data parsed request data |
||
1381 | * @return WP_Error|array error or edited note response data |
||
1382 | */ |
||
1383 | public function edit_order_note( $order_id, $id, $data ) { |
||
1445 | |||
1446 | /** |
||
1447 | * Delete order note |
||
1448 | * |
||
1449 | * @since 2.2 |
||
1450 | * @param string $order_id order ID |
||
1451 | * @param string $id note ID |
||
1452 | * @return WP_Error|array error or deleted message |
||
1453 | */ |
||
1454 | public function delete_order_note( $order_id, $id ) { |
||
1495 | |||
1496 | /** |
||
1497 | * Get the order refunds for an order |
||
1498 | * |
||
1499 | * @since 2.2 |
||
1500 | * @param string $order_id order ID |
||
1501 | * @param string|null $fields fields to include in response |
||
1502 | * @return array |
||
1503 | */ |
||
1504 | public function get_order_refunds( $order_id, $fields = null ) { |
||
1527 | |||
1528 | /** |
||
1529 | * Get an order refund for the given order ID and ID |
||
1530 | * |
||
1531 | * @since 2.2 |
||
1532 | * @param string $order_id order ID |
||
1533 | * @param string|null $fields fields to limit response to |
||
1534 | * @return array |
||
1535 | */ |
||
1536 | public function get_order_refund( $order_id, $id, $fields = null ) { |
||
1605 | |||
1606 | /** |
||
1607 | * Create a new order refund for the given order |
||
1608 | * |
||
1609 | * @since 2.2 |
||
1610 | * @param string $order_id order ID |
||
1611 | * @param array $data raw request data |
||
1612 | * @param bool $api_refund do refund using a payment gateway API |
||
1613 | * @return WP_Error|array error or created refund response data |
||
1614 | */ |
||
1615 | public function create_order_refund( $order_id, $data, $api_refund = true ) { |
||
1682 | |||
1683 | /** |
||
1684 | * Edit an order refund |
||
1685 | * |
||
1686 | * @since 2.2 |
||
1687 | * @param string $order_id order ID |
||
1688 | * @param string $id refund ID |
||
1689 | * @param array $data parsed request data |
||
1690 | * @return WP_Error|array error or edited refund response data |
||
1691 | */ |
||
1692 | public function edit_order_refund( $order_id, $id, $data ) { |
||
1749 | |||
1750 | /** |
||
1751 | * Delete order refund |
||
1752 | * |
||
1753 | * @since 2.2 |
||
1754 | * @param string $order_id order ID |
||
1755 | * @param string $id refund ID |
||
1756 | * @return WP_Error|array error or deleted message |
||
1757 | */ |
||
1758 | public function delete_order_refund( $order_id, $id ) { |
||
1794 | |||
1795 | /** |
||
1796 | * Bulk update or insert orders |
||
1797 | * Accepts an array with orders in the formats supported by |
||
1798 | * WC_API_Orders->create_order() and WC_API_Orders->edit_order() |
||
1799 | * |
||
1800 | * @since 2.4.0 |
||
1801 | * @param array $data |
||
1802 | * @return array |
||
1803 | */ |
||
1804 | View Code Duplication | public function bulk( $data ) { |
|
1863 | } |
||
1864 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.