Test Failed
Push — master ( a5e4c3...7495fa )
by Mike
43:00
created

WC_REST_Orders_Controller::prepare_objects_query()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 1
dl 0
loc 23
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * REST API Orders controller
4
 *
5
 * Handles requests to the /orders endpoint.
6
 *
7
 * @package WooCommerce/RestApi
8
 * @since    2.6.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * REST API Orders controller class.
15
 *
16
 * @package WooCommerce/RestApi
17
 * @extends WC_REST_Orders_V2_Controller
18
 */
19
class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
20
21
	/**
22
	 * Endpoint namespace.
23
	 *
24
	 * @var string
25
	 */
26
	protected $namespace = 'wc/v3';
27
28
	/**
29
	 * Calculate coupons.
30
	 *
31
	 * @throws WC_REST_Exception When fails to set any item.
32
	 * @param WP_REST_Request $request Request object.
0 ignored issues
show
Bug introduced by
The type WP_REST_Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
	 * @param WC_Order        $order   Order data.
0 ignored issues
show
Bug introduced by
The type WC_Order was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
	 * @return bool
35
	 */
36
	protected function calculate_coupons( $request, $order ) {
37
		if ( ! isset( $request['coupon_lines'] ) || ! is_array( $request['coupon_lines'] ) ) {
38
			return false;
39
		}
40
41
		// Remove all coupons first to ensure calculation is correct.
42
		foreach ( $order->get_items( 'coupon' ) as $coupon ) {
43
			$order->remove_coupon( $coupon->get_code() );
44
		}
45
46
		foreach ( $request['coupon_lines'] as $item ) {
47
			if ( is_array( $item ) ) {
48
				if ( empty( $item['id'] ) ) {
49
					if ( empty( $item['code'] ) ) {
50
						throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
0 ignored issues
show
Bug introduced by
The type WC_REST_Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
51
					}
52
53
					$results = $order->apply_coupon( wc_clean( $item['code'] ) );
54
55
					if ( is_wp_error( $results ) ) {
56
						throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 );
57
					}
58
				}
59
			}
60
		}
61
62
		return true;
63
	}
64
65
	/**
66
	 * Prepare a single order for create or update.
67
	 *
68
	 * @throws WC_REST_Exception When fails to set any item.
69
	 * @param  WP_REST_Request $request Request object.
70
	 * @param  bool            $creating If is creating a new object.
71
	 * @return WP_Error|WC_Data
0 ignored issues
show
Bug introduced by
The type WP_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type WC_Data was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
72
	 */
73
	protected function prepare_object_for_database( $request, $creating = false ) {
74
		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
75
		$order     = new WC_Order( $id );
76
		$schema    = $this->get_item_schema();
77
		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
78
79
		// Handle all writable props.
80
		foreach ( $data_keys as $key ) {
81
			$value = $request[ $key ];
82
83
			if ( ! is_null( $value ) ) {
84
				switch ( $key ) {
85
					case 'coupon_lines':
86
					case 'status':
87
						// Change should be done later so transitions have new data.
88
						break;
89
					case 'billing':
90
					case 'shipping':
91
						$this->update_address( $order, $value, $key );
92
						break;
93
					case 'line_items':
94
					case 'shipping_lines':
95
					case 'fee_lines':
96
						if ( is_array( $value ) ) {
97
							foreach ( $value as $item ) {
98
								if ( is_array( $item ) ) {
99
									if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
100
										$order->remove_item( $item['id'] );
101
									} else {
102
										$this->set_item( $order, $key, $item );
103
									}
104
								}
105
							}
106
						}
107
						break;
108
					case 'meta_data':
109
						if ( is_array( $value ) ) {
110
							foreach ( $value as $meta ) {
111
								$order->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
112
							}
113
						}
114
						break;
115
					default:
116
						if ( is_callable( array( $order, "set_{$key}" ) ) ) {
117
							$order->{"set_{$key}"}( $value );
118
						}
119
						break;
120
				}
121
			}
122
		}
123
124
		/**
125
		 * Filters an object before it is inserted via the REST API.
126
		 *
127
		 * The dynamic portion of the hook name, `$this->post_type`,
128
		 * refers to the object type slug.
129
		 *
130
		 * @param WC_Data         $order    Object object.
131
		 * @param WP_REST_Request $request  Request object.
132
		 * @param bool            $creating If is creating a new object.
133
		 */
134
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $order, $request, $creating );
135
	}
136
137
	/**
138
	 * Save an object data.
139
	 *
140
	 * @since  3.0.0
141
	 * @throws WC_REST_Exception But all errors are validated before returning any data.
142
	 * @param  WP_REST_Request $request  Full details about the request.
143
	 * @param  bool            $creating If is creating a new object.
144
	 * @return WC_Data|WP_Error
145
	 */
146
	protected function save_object( $request, $creating = false ) {
147
		try {
148
			$object = $this->prepare_object_for_database( $request, $creating );
149
150
			if ( is_wp_error( $object ) ) {
151
				return $object;
152
			}
153
154
			// Make sure gateways are loaded so hooks from gateways fire on save/create.
155
			WC()->payment_gateways();
156
157
			if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] ) {
158
				// Make sure customer exists.
159
				if ( false === get_user_by( 'id', $request['customer_id'] ) ) {
160
					throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 );
161
				}
162
163
				// Make sure customer is part of blog.
164
				if ( is_multisite() && ! is_user_member_of_blog( $request['customer_id'] ) ) {
165
					add_user_to_blog( get_current_blog_id(), $request['customer_id'], 'customer' );
166
				}
167
			}
168
169
			if ( $creating ) {
170
				$object->set_created_via( 'rest-api' );
171
				$object->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
172
				$object->calculate_totals();
173
			} else {
174
				// If items have changed, recalculate order totals.
175
				if ( isset( $request['billing'] ) || isset( $request['shipping'] ) || isset( $request['line_items'] ) || isset( $request['shipping_lines'] ) || isset( $request['fee_lines'] ) || isset( $request['coupon_lines'] ) ) {
176
					$object->calculate_totals( true );
177
				}
178
			}
179
180
			// Set coupons.
181
			$this->calculate_coupons( $request, $object );
182
183
			// Set status.
184
			if ( ! empty( $request['status'] ) ) {
185
				$object->set_status( $request['status'] );
186
			}
187
188
			$object->save();
189
190
			// Actions for after the order is saved.
191
			if ( true === $request['set_paid'] ) {
192
				if ( $creating || $object->needs_payment() ) {
193
					$object->payment_complete( $request['transaction_id'] );
194
				}
195
			}
196
197
			return $this->get_object( $object->get_id() );
198
		} catch ( WC_Data_Exception $e ) {
0 ignored issues
show
Bug introduced by
The type WC_Data_Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
199
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
200
		} catch ( WC_REST_Exception $e ) {
201
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
202
		}
203
	}
204
205
	/**
206
	 * Prepare objects query.
207
	 *
208
	 * @since  3.0.0
209
	 * @param  WP_REST_Request $request Full details about the request.
210
	 * @return array
211
	 */
212
	protected function prepare_objects_query( $request ) {
213
		// This is needed to get around an array to string notice in WC_REST_Orders_V2_Controller::prepare_objects_query.
214
		$statuses = $request['status'];
215
		unset( $request['status'] );
216
		$args = parent::prepare_objects_query( $request );
217
218
		$args['post_status'] = array();
219
		foreach ( $statuses as $status ) {
220
			if ( in_array( $status, $this->get_order_statuses(), true ) ) {
221
				$args['post_status'][] = 'wc-' . $status;
222
			} elseif ( 'any' === $status ) {
223
				// Set status to "any" and short-circuit out.
224
				$args['post_status'] = 'any';
225
				break;
226
			} else {
227
				$args['post_status'][] = $status;
228
			}
229
		}
230
231
		// Put the statuses back for further processing (next/prev links, etc).
232
		$request['status'] = $statuses;
233
234
		return $args;
235
	}
236
237
	/**
238
	 * Get the Order's schema, conforming to JSON Schema.
239
	 *
240
	 * @return array
241
	 */
242
	public function get_item_schema() {
243
		$schema = parent::get_item_schema();
244
245
		$schema['properties']['coupon_lines']['items']['properties']['discount']['readonly'] = true;
246
247
		return $schema;
248
	}
249
250
	/**
251
	 * Get the query params for collections.
252
	 *
253
	 * @return array
254
	 */
255
	public function get_collection_params() {
256
		$params = parent::get_collection_params();
257
258
		$params['status'] = array(
259
			'default'           => 'any',
260
			'description'       => __( 'Limit result set to orders which have specific statuses.', 'woocommerce' ),
261
			'type'              => 'array',
262
			'items'             => array(
263
				'type' => 'string',
264
				'enum' => array_merge( array( 'any', 'trash' ), $this->get_order_statuses() ),
265
			),
266
			'validate_callback' => 'rest_validate_request_arg',
267
		);
268
269
		return $params;
270
	}
271
}
272