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

includes/abstracts/abstract-wc-rest-controller.php (2 issues)

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 ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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