Completed
Push — master ( 15aa29...17da96 )
by Claudio
18:39 queued 11s
created

includes/abstracts/abstract-wc-rest-controller.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * REST Controller
4
 *
5
 * This class extend `WP_REST_Controller` in order to include /batch endpoint
6
 * for almost all endpoints in WooCommerce REST API.
7
 *
8
 * It's required to follow "Controller Classes" guide before extending this class:
9
 * <https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/>
10
 *
11
 * NOTE THAT ONLY CODE RELEVANT FOR MOST ENDPOINTS SHOULD BE INCLUDED INTO THIS CLASS.
12
 * If necessary extend this class and create new abstract classes like `WC_REST_CRUD_Controller` or `WC_REST_Terms_Controller`.
13
 *
14
 * @class   WC_REST_Controller
15
 * @package WooCommerce/Abstracts
16
 * @see     https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/
17
 */
18
19 1
if ( ! defined( 'ABSPATH' ) ) {
20
	exit;
21
}
22
23
/**
24
 * Abstract Rest Controller Class
25
 *
26
 * @package  WooCommerce/Abstracts
27
 * @extends  WP_REST_Controller
28
 * @version  2.6.0
29
 */
30
abstract class WC_REST_Controller extends WP_REST_Controller {
31
32
	/**
33
	 * Endpoint namespace.
34
	 *
35
	 * @var string
36
	 */
37
	protected $namespace = 'wc/v1';
38
39
	/**
40
	 * Route base.
41
	 *
42
	 * @var string
43
	 */
44
	protected $rest_base = '';
45
46
	/**
47
	 * Add the schema from additional fields to an schema array.
48
	 *
49
	 * The type of object is inferred from the passed schema.
50
	 *
51
	 * @param array $schema Schema array.
52
	 *
53
	 * @return array
54
	 */
55 426
	protected function add_additional_fields_schema( $schema ) {
56 426
		if ( empty( $schema['title'] ) ) {
57
			return $schema;
58
		}
59
60
		/**
61
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
62
		 */
63 426
		$object_type = $schema['title'];
64
65 426
		$additional_fields = $this->get_additional_fields( $object_type );
66
67 426
		foreach ( $additional_fields as $field_name => $field_options ) {
68
			if ( ! $field_options['schema'] ) {
69
				continue;
70
			}
71
72
			$schema['properties'][ $field_name ] = $field_options['schema'];
73
		}
74
75 426
		$schema['properties'] = apply_filters( 'woocommerce_rest_' . $object_type . '_schema', $schema['properties'] );
76
77 426
		return $schema;
78
	}
79
80
	/**
81
	 * Get normalized rest base.
82
	 *
83
	 * @return string
84
	 */
85 14
	protected function get_normalized_rest_base() {
86 14
		return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
87
	}
88
89
	/**
90
	 * Check batch limit.
91
	 *
92
	 * @param array $items Request items.
93
	 * @return bool|WP_Error
94
	 */
95 14
	protected function check_batch_limit( $items ) {
96 14
		$limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
97 14
		$total = 0;
98
99 14
		if ( ! empty( $items['create'] ) ) {
100 10
			$total += count( $items['create'] );
101
		}
102
103 14
		if ( ! empty( $items['update'] ) ) {
104 14
			$total += count( $items['update'] );
105
		}
106
107 14
		if ( ! empty( $items['delete'] ) ) {
108 12
			$total += count( $items['delete'] );
109
		}
110
111 14
		if ( $total > $limit ) {
112
			/* translators: %s: items limit */
113
			return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), array( 'status' => 413 ) );
114
		}
115
116 14
		return true;
117
	}
118
119
	/**
120
	 * Bulk create, update and delete items.
121
	 *
122
	 * @param WP_REST_Request $request Full details about the request.
123
	 * @return array Of WP_Error or WP_REST_Response.
124
	 */
125 14
	public function batch_items( $request ) {
126
		/**
127
		 * REST Server
128
		 *
129
		 * @var WP_REST_Server $wp_rest_server
130
		 */
131
		global $wp_rest_server;
132
133
		// Get the request params.
134 14
		$items    = array_filter( $request->get_params() );
135 14
		$response = array();
136
137
		// Check batch limit.
138 14
		$limit = $this->check_batch_limit( $items );
139 14
		if ( is_wp_error( $limit ) ) {
140
			return $limit;
141
		}
142
143 14
		if ( ! empty( $items['create'] ) ) {
144 10
			foreach ( $items['create'] as $item ) {
145 10
				$_item = new WP_REST_Request( 'POST' );
146
147
				// Default parameters.
148 10
				$defaults = array();
149 10
				$schema   = $this->get_public_item_schema();
150 10
				foreach ( $schema['properties'] as $arg => $options ) {
151 10
					if ( isset( $options['default'] ) ) {
152 8
						$defaults[ $arg ] = $options['default'];
153
					}
154
				}
155 10
				$_item->set_default_params( $defaults );
156
157
				// Set request parameters.
158 10
				$_item->set_body_params( $item );
159 10
				$_response = $this->create_item( $_item );
160
161 10 View Code Duplication
				if ( is_wp_error( $_response ) ) {
162
					$response['create'][] = array(
163
						'id'    => 0,
164
						'error' => array(
165
							'code'    => $_response->get_error_code(),
166
							'message' => $_response->get_error_message(),
167
							'data'    => $_response->get_error_data(),
168
						),
169
					);
170
				} else {
171 10
					$response['create'][] = $wp_rest_server->response_to_data( $_response, '' );
172
				}
173
			}
174
		}
175
176 14
		if ( ! empty( $items['update'] ) ) {
177 14
			foreach ( $items['update'] as $item ) {
178 14
				$_item = new WP_REST_Request( 'PUT' );
179 14
				$_item->set_body_params( $item );
180 14
				$_response = $this->update_item( $_item );
181
182 14 View Code Duplication
				if ( is_wp_error( $_response ) ) {
183
					$response['update'][] = array(
184
						'id'    => $item['id'],
185
						'error' => array(
186
							'code'    => $_response->get_error_code(),
187
							'message' => $_response->get_error_message(),
188
							'data'    => $_response->get_error_data(),
189
						),
190
					);
191
				} else {
192 14
					$response['update'][] = $wp_rest_server->response_to_data( $_response, '' );
193
				}
194
			}
195
		}
196
197 14
		if ( ! empty( $items['delete'] ) ) {
198 12
			foreach ( $items['delete'] as $id ) {
199 12
				$id = (int) $id;
200
201 12
				if ( 0 === $id ) {
202
					continue;
203
				}
204
205 12
				$_item = new WP_REST_Request( 'DELETE' );
206 12
				$_item->set_query_params(
207
					array(
208 12
						'id'    => $id,
209
						'force' => true,
210
					)
211
				);
212 12
				$_response = $this->delete_item( $_item );
213
214 12 View Code Duplication
				if ( is_wp_error( $_response ) ) {
215
					$response['delete'][] = array(
216
						'id'    => $id,
217
						'error' => array(
218
							'code'    => $_response->get_error_code(),
219
							'message' => $_response->get_error_message(),
220
							'data'    => $_response->get_error_data(),
221
						),
222
					);
223
				} else {
224 12
					$response['delete'][] = $wp_rest_server->response_to_data( $_response, '' );
225
				}
226
			}
227
		}
228
229 14
		return $response;
230
	}
231
232
	/**
233
	 * Validate a text value for a text based setting.
234
	 *
235
	 * @since 3.0.0
236
	 * @param string $value Value.
237
	 * @param array  $setting Setting.
238
	 * @return string
239
	 */
240 14
	public function validate_setting_text_field( $value, $setting ) {
241 14
		$value = is_null( $value ) ? '' : $value;
242 14
		return wp_kses_post( trim( stripslashes( $value ) ) );
243
	}
244
245
	/**
246
	 * Validate select based settings.
247
	 *
248
	 * @since 3.0.0
249
	 * @param string $value Value.
250
	 * @param array  $setting Setting.
251
	 * @return string|WP_Error
252
	 */
253 14
	public function validate_setting_select_field( $value, $setting ) {
254 14
		if ( array_key_exists( $value, $setting['options'] ) ) {
255 14
			return $value;
256
		} else {
257 8
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
258
		}
259
	}
260
261
	/**
262
	 * Validate multiselect based settings.
263
	 *
264
	 * @since 3.0.0
265
	 * @param array $values Values.
266
	 * @param array $setting Setting.
267
	 * @return array|WP_Error
268
	 */
269 2
	public function validate_setting_multiselect_field( $values, $setting ) {
270 2
		if ( empty( $values ) ) {
271
			return array();
272
		}
273
274 2
		if ( ! is_array( $values ) ) {
275
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
276
		}
277
278 2
		$final_values = array();
279 2
		foreach ( $values as $value ) {
280 2
			if ( array_key_exists( $value, $setting['options'] ) ) {
281 2
				$final_values[] = $value;
282
			}
283
		}
284
285 2
		return $final_values;
286
	}
287
288
	/**
289
	 * Validate image_width based settings.
290
	 *
291
	 * @since 3.0.0
292
	 * @param array $values Values.
293
	 * @param array $setting Setting.
294
	 * @return string|WP_Error
295
	 */
296
	public function validate_setting_image_width_field( $values, $setting ) {
297
		if ( ! is_array( $values ) ) {
298
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
299
		}
300
301
		$current = $setting['value'];
302
		if ( isset( $values['width'] ) ) {
303
			$current['width'] = intval( $values['width'] );
304
		}
305
		if ( isset( $values['height'] ) ) {
306
			$current['height'] = intval( $values['height'] );
307
		}
308
		if ( isset( $values['crop'] ) ) {
309
			$current['crop'] = (bool) $values['crop'];
310
		}
311
		return $current;
312
	}
313
314
	/**
315
	 * Validate radio based settings.
316
	 *
317
	 * @since 3.0.0
318
	 * @param string $value Value.
319
	 * @param array  $setting Setting.
320
	 * @return string|WP_Error
321
	 */
322 2
	public function validate_setting_radio_field( $value, $setting ) {
323 2
		return $this->validate_setting_select_field( $value, $setting );
324
	}
325
326
	/**
327
	 * Validate checkbox based settings.
328
	 *
329
	 * @since 3.0.0
330
	 * @param string $value Value.
331
	 * @param array  $setting Setting.
332
	 * @return string|WP_Error
333
	 */
334 4
	public function validate_setting_checkbox_field( $value, $setting ) {
335 4
		if ( in_array( $value, array( 'yes', 'no' ) ) ) {
336 4
			return $value;
337 2
		} elseif ( empty( $value ) ) {
338
			$value = isset( $setting['default'] ) ? $setting['default'] : 'no';
339
			return $value;
340
		} else {
341 2
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
342
		}
343
	}
344
345
	/**
346
	 * Validate textarea based settings.
347
	 *
348
	 * @since 3.0.0
349
	 * @param string $value Value.
350
	 * @param array  $setting Setting.
351
	 * @return string
352
	 */
353 View Code Duplication
	public function validate_setting_textarea_field( $value, $setting ) {
354
		$value = is_null( $value ) ? '' : $value;
355
		return wp_kses(
356
			trim( stripslashes( $value ) ),
357
			array_merge(
358
				array(
359
					'iframe' => array(
360
						'src'   => true,
361
						'style' => true,
362
						'id'    => true,
363
						'class' => true,
364
					),
365
				),
366
				wp_kses_allowed_html( 'post' )
367
			)
368
		);
369
	}
370
371
	/**
372
	 * Add meta query.
373
	 *
374
	 * @since 3.0.0
375
	 * @param array $args       Query args.
376
	 * @param array $meta_query Meta query.
377
	 * @return array
378
	 */
379
	protected function add_meta_query( $args, $meta_query ) {
380
		if ( empty( $args['meta_query'] ) ) {
381
			$args['meta_query'] = array();
0 ignored issues
show
Detected usage of meta_query, possible slow query.
Loading history...
382
		}
383
384
		$args['meta_query'][] = $meta_query;
385
386
		return $args['meta_query'];
387
	}
388
389
	/**
390
	 * Get the batch schema, conforming to JSON Schema.
391
	 *
392
	 * @return array
393
	 */
394
	public function get_public_batch_schema() {
395
		$schema = array(
396
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
397
			'title'      => 'batch',
398
			'type'       => 'object',
399
			'properties' => array(
400
				'create' => array(
401
					'description' => __( 'List of created resources.', 'woocommerce' ),
402
					'type'        => 'array',
403
					'context'     => array( 'view', 'edit' ),
404
					'items'       => array(
405
						'type'    => 'object',
406
					),
407
				),
408
				'update' => array(
409
					'description' => __( 'List of updated resources.', 'woocommerce' ),
410
					'type'        => 'array',
411
					'context'     => array( 'view', 'edit' ),
412
					'items'       => array(
413
						'type'    => 'object',
414
					),
415
				),
416
				'delete' => array(
417
					'description' => __( 'List of delete resources.', 'woocommerce' ),
418
					'type'        => 'array',
419
					'context'     => array( 'view', 'edit' ),
420
					'items'       => array(
421
						'type'    => 'integer',
422
					),
423
				),
424
			),
425
		);
426
427
		return $schema;
428
	}
429
430
	/**
431
	 * Gets an array of fields to be included on the response.
432
	 * Included fields are based on item schema and `_fields=` request argument.
433
	 * Introduced to support WordPress 4.9.6 changes.
434
	 *
435
	 * @since 3.5.0
436
	 * @param WP_REST_Request $request Full details about the request.
437
	 * @return array Fields to be included in the response.
438
	 */
439 194
	public function get_fields_for_response( $request ) {
440 194
		$schema = $this->get_item_schema();
441 194
		$fields = isset( $schema['properties'] ) ? array_keys( $schema['properties'] ) : array();
442
443 194
		$additional_fields = $this->get_additional_fields();
444 194
		foreach ( $additional_fields as $field_name => $field_options ) {
445
			// For back-compat, include any field with an empty schema
446
			// because it won't be present in $this->get_item_schema().
447
			if ( is_null( $field_options['schema'] ) ) {
448
				$fields[] = $field_name;
449
			}
450
		}
451
452 194
		if ( ! isset( $request['_fields'] ) ) {
453 194
			return $fields;
454
		}
455 3
		$requested_fields = is_array( $request['_fields'] ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] );
456 3
		if ( 0 === count( $requested_fields ) ) {
457
			return $fields;
458
		}
459
		// Trim off outside whitespace from the comma delimited list.
460 3
		$requested_fields = array_map( 'trim', $requested_fields );
461
		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
462 3
		if ( in_array( 'id', $fields, true ) ) {
463 3
			$requested_fields[] = 'id';
464
		}
465 3
		return array_intersect( $fields, $requested_fields );
466
	}
467
}
468