WP_REST_Controller::get_items()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 1
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * @version 2.0-beta13
9
 */
10
abstract class WP_REST_Controller {
11
12
	/**
13
	 * The namespace of this controller's route.
14
	 *
15
	 * @var string
16
	 */
17
	protected $namespace;
18
19
	/**
20
	 * The base of this controller's route.
21
	 *
22
	 * @var string
23
	 */
24
	protected $rest_base;
25
26
	/**
27
	 * Register the routes for the objects of the controller.
28
	 */
29
	public function register_routes() {
30
		_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden', 'woocommerce' ), 'WPAPI-2.0' );
31
	}
32
33
	/**
34
	 * Check if a given request has access to get items.
35
	 *
36
	 * @param WP_REST_Request $request Full data about the request.
37
	 * @return WP_Error|boolean
38
	 */
39
	public function get_items_permissions_check( $request ) {
40
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
41
	}
42
43
	/**
44
	 * Get a collection of items.
45
	 *
46
	 * @param WP_REST_Request $request Full data about the request.
47
	 * @return WP_Error|WP_REST_Response
48
	 */
49
	public function get_items( $request ) {
50
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
51
	}
52
53
	/**
54
	 * Check if a given request has access to get a specific item.
55
	 *
56
	 * @param WP_REST_Request $request Full data about the request.
57
	 * @return WP_Error|boolean
58
	 */
59
	public function get_item_permissions_check( $request ) {
60
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
61
	}
62
63
	/**
64
	 * Get one item from the collection.
65
	 *
66
	 * @param WP_REST_Request $request Full data about the request.
67
	 * @return WP_Error|WP_REST_Response
68
	 */
69
	public function get_item( $request ) {
70
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
71
	}
72
73
	/**
74
	 * Check if a given request has access to create items.
75
	 *
76
	 * @param WP_REST_Request $request Full data about the request.
77
	 * @return WP_Error|boolean
78
	 */
79
	public function create_item_permissions_check( $request ) {
80
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
81
	}
82
83
	/**
84
	 * Create one item from the collection.
85
	 *
86
	 * @param WP_REST_Request $request Full data about the request.
87
	 * @return WP_Error|WP_REST_Response
88
	 */
89
	public function create_item( $request ) {
90
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
91
	}
92
93
	/**
94
	 * Check if a given request has access to update a specific item.
95
	 *
96
	 * @param WP_REST_Request $request Full data about the request.
97
	 * @return WP_Error|boolean
98
	 */
99
	public function update_item_permissions_check( $request ) {
100
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
101
	}
102
103
	/**
104
	 * Update one item from the collection.
105
	 *
106
	 * @param WP_REST_Request $request Full data about the request.
107
	 * @return WP_Error|WP_REST_Response
108
	 */
109
	public function update_item( $request ) {
110
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
111
	}
112
113
	/**
114
	 * Check if a given request has access to delete a specific item.
115
	 *
116
	 * @param WP_REST_Request $request Full data about the request.
117
	 * @return WP_Error|boolean
118
	 */
119
	public function delete_item_permissions_check( $request ) {
120
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
121
	}
122
123
	/**
124
	 * Delete one item from the collection.
125
	 *
126
	 * @param WP_REST_Request $request Full data about the request.
127
	 * @return WP_Error|WP_REST_Response
128
	 */
129
	public function delete_item( $request ) {
130
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
131
	}
132
133
	/**
134
	 * Prepare the item for create or update operation.
135
	 *
136
	 * @param WP_REST_Request $request Request object.
137
	 * @return WP_Error|object $prepared_item
138
	 */
139
	protected function prepare_item_for_database( $request ) {
140
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
141
	}
142
143
	/**
144
	 * Prepare the item for the REST response.
145
	 *
146
	 * @param mixed $item WordPress representation of the item.
147
	 * @param WP_REST_Request $request Request object.
148
	 * @return WP_REST_Response $response
149
	 */
150
	public function prepare_item_for_response( $item, $request ) {
151
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
152
	}
153
154
	/**
155
	 * Prepare a response for inserting into a collection.
156
	 *
157
	 * @param WP_REST_Response $response Response object.
158
	 * @return array Response data, ready for insertion into collection data.
159
	 */
160
	public function prepare_response_for_collection( $response ) {
161
		if ( ! ( $response instanceof WP_REST_Response ) ) {
162
			return $response;
163
		}
164
165
		$data = (array) $response->get_data();
166
		$server = rest_get_server();
167
168
		if ( method_exists( $server, 'get_compact_response_links' ) ) {
169
			$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
170
		} else {
171
			$links = call_user_func( array( $server, 'get_response_links' ), $response );
172
		}
173
174
		if ( ! empty( $links ) ) {
175
			$data['_links'] = $links;
176
		}
177
178
		return $data;
179
	}
180
181
	/**
182
	 * Filter a response based on the context defined in the schema.
183
	 *
184
	 * @param array $data
185
	 * @param string $context
186
	 * @return array
187
	 */
188
	public function filter_response_by_context( $data, $context ) {
189
190
		$schema = $this->get_item_schema();
191
		foreach ( $data as $key => $value ) {
192
			if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
193
				continue;
194
			}
195
196
			if ( ! in_array( $context, $schema['properties'][ $key ]['context'] ) ) {
197
				unset( $data[ $key ] );
198
			}
199
200
			if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
201
				foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
202
					if ( empty( $details['context'] ) ) {
203
						continue;
204
					}
205
					if ( ! in_array( $context, $details['context'] ) ) {
206
						if ( isset( $data[ $key ][ $attribute ] ) ) {
207
							unset( $data[ $key ][ $attribute ] );
208
						}
209
					}
210
				}
211
			}
212
		}
213
214
		return $data;
215
	}
216
217
	/**
218
	 * Get the item's schema, conforming to JSON Schema.
219
	 *
220
	 * @return array
221
	 */
222
	public function get_item_schema() {
223
		return $this->add_additional_fields_schema( array() );
224
	}
225
226
	/**
227
	 * Get the item's schema for display / public consumption purposes.
228
	 *
229
	 * @return array
230
	 */
231
	public function get_public_item_schema() {
232
233
		$schema = $this->get_item_schema();
234
235
		foreach ( $schema['properties'] as &$property ) {
236
			if ( isset( $property['arg_options'] ) ) {
237
				unset( $property['arg_options'] );
238
			}
239
		}
240
241
		return $schema;
242
	}
243
244
	/**
245
	 * Get the query params for collections.
246
	 *
247
	 * @return array
248
	 */
249
	public function get_collection_params() {
250
		return array(
251
			'context'                => $this->get_context_param(),
252
			'page'                   => array(
253
				'description'        => __( 'Current page of the collection.', 'woocommerce' ),
254
				'type'               => 'integer',
255
				'default'            => 1,
256
				'sanitize_callback'  => 'absint',
257
				'validate_callback'  => 'rest_validate_request_arg',
258
				'minimum'            => 1,
259
			),
260
			'per_page'               => array(
261
				'description'        => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
262
				'type'               => 'integer',
263
				'default'            => 10,
264
				'minimum'            => 1,
265
				'maximum'            => 100,
266
				'sanitize_callback'  => 'absint',
267
				'validate_callback'  => 'rest_validate_request_arg',
268
			),
269
			'search'                 => array(
270
				'description'        => __( 'Limit results to those matching a string.', 'woocommerce' ),
271
				'type'               => 'string',
272
				'sanitize_callback'  => 'sanitize_text_field',
273
				'validate_callback'  => 'rest_validate_request_arg',
274
			),
275
		);
276
	}
277
278
	/**
279
	 * Get the magical context param.
280
	 *
281
	 * Ensures consistent description between endpoints, and populates enum from schema.
282
	 *
283
	 * @param array     $args
284
	 * @return array
285
	 */
286
	public function get_context_param( $args = array() ) {
287
		$param_details = array(
288
			'description'        => __( 'Scope under which the request is made; determines fields present in response.', 'woocommerce' ),
289
			'type'               => 'string',
290
			'sanitize_callback'  => 'sanitize_key',
291
			'validate_callback'  => 'rest_validate_request_arg',
292
		);
293
		$schema = $this->get_item_schema();
294
		if ( empty( $schema['properties'] ) ) {
295
			return array_merge( $param_details, $args );
296
		}
297
		$contexts = array();
298
		foreach ( $schema['properties'] as $key => $attributes ) {
299
			if ( ! empty( $attributes['context'] ) ) {
300
				$contexts = array_merge( $contexts, $attributes['context'] );
301
			}
302
		}
303
		if ( ! empty( $contexts ) ) {
304
			$param_details['enum'] = array_unique( $contexts );
305
			rsort( $param_details['enum'] );
306
		}
307
		return array_merge( $param_details, $args );
308
	}
309
310
	/**
311
	 * Add the values from additional fields to a data object.
312
	 *
313
	 * @param array  $object
314
	 * @param WP_REST_Request $request
315
	 * @return array modified object with additional fields.
316
	 */
317
	protected function add_additional_fields_to_object( $object, $request ) {
318
319
		$additional_fields = $this->get_additional_fields();
320
321
		foreach ( $additional_fields as $field_name => $field_options ) {
322
323
			if ( ! $field_options['get_callback'] ) {
324
				continue;
325
			}
326
327
			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
328
		}
329
330
		return $object;
331
	}
332
333
	/**
334
	 * Update the values of additional fields added to a data object.
335
	 *
336
	 * @param array  $object
337
	 * @param WP_REST_Request $request
338
	 */
339
	protected function update_additional_fields_for_object( $object, $request ) {
340
341
		$additional_fields = $this->get_additional_fields();
342
343
		foreach ( $additional_fields as $field_name => $field_options ) {
344
345
			if ( ! $field_options['update_callback'] ) {
346
				continue;
347
			}
348
349
			// Don't run the update callbacks if the data wasn't passed in the request.
350
			if ( ! isset( $request[ $field_name ] ) ) {
351
				continue;
352
			}
353
354
			call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
355
		}
356
	}
357
358
	/**
359
	 * Add the schema from additional fields to an schema array.
360
	 *
361
	 * The type of object is inferred from the passed schema.
362
	 *
363
	 * @param array $schema Schema array.
364
	 */
365
	protected function add_additional_fields_schema( $schema ) {
366
		if ( empty( $schema['title'] ) ) {
367
			return $schema;
368
		}
369
370
		/**
371
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
372
		 */
373
		$object_type = $schema['title'];
374
375
		$additional_fields = $this->get_additional_fields( $object_type );
376
377 View Code Duplication
		foreach ( $additional_fields as $field_name => $field_options ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
378
			if ( ! $field_options['schema'] ) {
379
				continue;
380
			}
381
382
			$schema['properties'][ $field_name ] = $field_options['schema'];
383
		}
384
385
		return $schema;
386
	}
387
388
	/**
389
	 * Get all the registered additional fields for a given object-type.
390
	 *
391
	 * @param  string $object_type
392
	 * @return array
393
	 */
394
	protected function get_additional_fields( $object_type = null ) {
395
396
		if ( ! $object_type ) {
397
			$object_type = $this->get_object_type();
398
		}
399
400
		if ( ! $object_type ) {
401
			return array();
402
		}
403
404
		global $wp_rest_additional_fields;
405
406
		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
407
			return array();
408
		}
409
410
		return $wp_rest_additional_fields[ $object_type ];
411
	}
412
413
	/**
414
	 * Get the object type this controller is responsible for managing.
415
	 *
416
	 * @return string
417
	 */
418
	protected function get_object_type() {
419
		$schema = $this->get_item_schema();
420
421
		if ( ! $schema || ! isset( $schema['title'] ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $schema of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
422
			return null;
423
		}
424
425
		return $schema['title'];
426
	}
427
428
	/**
429
	 * Get an array of endpoint arguments from the item schema for the controller.
430
	 *
431
	 * @param string $method HTTP method of the request. The arguments
432
	 *                       for `CREATABLE` requests are checked for required
433
	 *                       values and may fall-back to a given default, this
434
	 *                       is not done on `EDITABLE` requests. Default is
435
	 *                       WP_REST_Server::CREATABLE.
436
	 * @return array $endpoint_args
437
	 */
438
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
439
440
		$schema                = $this->get_item_schema();
441
		$schema_properties     = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
442
		$endpoint_args = array();
443
444
		foreach ( $schema_properties as $field_id => $params ) {
445
446
			// Arguments specified as `readonly` are not allowed to be set.
447
			if ( ! empty( $params['readonly'] ) ) {
448
				continue;
449
			}
450
451
			$endpoint_args[ $field_id ] = array(
452
				'validate_callback' => 'rest_validate_request_arg',
453
				'sanitize_callback' => 'rest_sanitize_request_arg',
454
			);
455
456
			if ( isset( $params['description'] ) ) {
457
				$endpoint_args[ $field_id ]['description'] = $params['description'];
458
			}
459
460
			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
461
				$endpoint_args[ $field_id ]['default'] = $params['default'];
462
			}
463
464
			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
465
				$endpoint_args[ $field_id ]['required'] = true;
466
			}
467
468
			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
469
				if ( isset( $params[ $schema_prop ] ) ) {
470
					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
471
				}
472
			}
473
474
			// Merge in any options provided by the schema property.
475
			if ( isset( $params['arg_options'] ) ) {
476
477
				// Only use required / default from arg_options on CREATABLE endpoints.
478
				if ( WP_REST_Server::CREATABLE !== $method ) {
479
					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
480
				}
481
482
				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
483
			}
484
		}
485
486
		return $endpoint_args;
487
	}
488
489
}
490