Completed
Push — trunk ( b21e44...2b7c73 )
by Justin
39:52 queued 25:17
created

WP_REST_Controller::get_item()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
4
abstract class WP_REST_Controller {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
5
6
	/**
7
	 * The namespace of this controller's route.
8
	 *
9
	 * @var string
10
	 */
11
	protected $namespace;
12
13
	/**
14
	 * The base of this controller's route.
15
	 *
16
	 * @var string
17
	 */
18
	protected $rest_base;
19
20
	/**
21
	 * Register the routes for the objects of the controller.
22
	 */
23
	public function register_routes() {
24
		_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden' ), 'WPAPI-2.0' );
25
	}
26
27
	/**
28
	 * Check if a given request has access to get items.
29
	 *
30
	 * @param WP_REST_Request $request Full data about the request.
31
	 * @return WP_Error|boolean
32
	 */
33
	public function get_items_permissions_check( $request ) {
34
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
35
	}
36
37
	/**
38
	 * Get a collection of items.
39
	 *
40
	 * @param WP_REST_Request $request Full data about the request.
41
	 * @return WP_Error|WP_REST_Response
42
	 */
43
	public function get_items( $request ) {
44
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
45
	}
46
47
	/**
48
	 * Check if a given request has access to get a specific item.
49
	 *
50
	 * @param WP_REST_Request $request Full data about the request.
51
	 * @return WP_Error|boolean
52
	 */
53
	public function get_item_permissions_check( $request ) {
54
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
55
	}
56
57
	/**
58
	 * Get one item from the collection.
59
	 *
60
	 * @param WP_REST_Request $request Full data about the request.
61
	 * @return WP_Error|WP_REST_Response
62
	 */
63
	public function get_item( $request ) {
64
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
65
	}
66
67
	/**
68
	 * Check if a given request has access to create items.
69
	 *
70
	 * @param WP_REST_Request $request Full data about the request.
71
	 * @return WP_Error|boolean
72
	 */
73
	public function create_item_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
74
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
75
	}
76
77
	/**
78
	 * Create one item from the collection.
79
	 *
80
	 * @param WP_REST_Request $request Full data about the request.
81
	 * @return WP_Error|WP_REST_Response
82
	 */
83
	public function create_item( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
85
	}
86
87
	/**
88
	 * Check if a given request has access to update a specific item.
89
	 *
90
	 * @param WP_REST_Request $request Full data about the request.
91
	 * @return WP_Error|boolean
92
	 */
93
	public function update_item_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
94
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
95
	}
96
97
	/**
98
	 * Update one item from the collection.
99
	 *
100
	 * @param WP_REST_Request $request Full data about the request.
101
	 * @return WP_Error|WP_REST_Response
102
	 */
103
	public function update_item( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
104
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
105
	}
106
107
	/**
108
	 * Check if a given request has access to delete a specific item.
109
	 *
110
	 * @param WP_REST_Request $request Full data about the request.
111
	 * @return WP_Error|boolean
112
	 */
113
	public function delete_item_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
114
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
115
	}
116
117
	/**
118
	 * Delete one item from the collection.
119
	 *
120
	 * @param WP_REST_Request $request Full data about the request.
121
	 * @return WP_Error|WP_REST_Response
122
	 */
123
	public function delete_item( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
124
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
125
	}
126
127
	/**
128
	 * Prepare the item for create or update operation.
129
	 *
130
	 * @param WP_REST_Request $request Request object.
131
	 * @return WP_Error|object $prepared_item
132
	 */
133
	protected function prepare_item_for_database( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
134
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
135
	}
136
137
	/**
138
	 * Prepare the item for the REST response.
139
	 *
140
	 * @param mixed $item WordPress representation of the item.
141
	 * @param WP_REST_Request $request Request object.
142
	 * @return WP_REST_Response $response
143
	 */
144
	public function prepare_item_for_response( $item, $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
146
	}
147
148
	/**
149
	 * Prepare a response for inserting into a collection.
150
	 *
151
	 * @param WP_REST_Response $response Response object.
152
	 * @return array Response data, ready for insertion into collection data.
153
	 */
154
	public function prepare_response_for_collection( $response ) {
155
		if ( ! ( $response instanceof WP_REST_Response ) ) {
0 ignored issues
show
Bug introduced by
The class WP_REST_Response does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
156
			return $response;
157
		}
158
159
		$data = (array) $response->get_data();
160
		$server = rest_get_server();
161
162
		if ( method_exists( $server, 'get_compact_response_links' ) ) {
163
			$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
164
		} else {
165
			$links = call_user_func( array( $server, 'get_response_links' ), $response );
166
		}
167
168
		if ( ! empty( $links ) ) {
169
			$data['_links'] = $links;
170
		}
171
172
		return $data;
173
	}
174
175
	/**
176
	 * Filter a response based on the context defined in the schema.
177
	 *
178
	 * @param array $data
179
	 * @param string $context
180
	 * @return array
181
	 */
182
	public function filter_response_by_context( $data, $context ) {
183
184
		$schema = $this->get_item_schema();
185
		foreach ( $data as $key => $value ) {
186
			if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
187
				continue;
188
			}
189
190
			if ( ! in_array( $context, $schema['properties'][ $key ]['context'] ) ) {
191
				unset( $data[ $key ] );
192
				continue;
193
			}
194
195
			if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
196
				foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
197
					if ( empty( $details['context'] ) ) {
198
						continue;
199
					}
200
					if ( ! in_array( $context, $details['context'] ) ) {
201
						if ( isset( $data[ $key ][ $attribute ] ) ) {
202
							unset( $data[ $key ][ $attribute ] );
203
						}
204
					}
205
				}
206
			}
207
		}
208
209
		return $data;
210
	}
211
212
	/**
213
	 * Get the item's schema, conforming to JSON Schema.
214
	 *
215
	 * @return array
216
	 */
217
	public function get_item_schema() {
218
		return $this->add_additional_fields_schema( array() );
219
	}
220
221
	/**
222
	 * Get the item's schema for display / public consumption purposes.
223
	 *
224
	 * @return array
225
	 */
226
	public function get_public_item_schema() {
227
228
		$schema = $this->get_item_schema();
229
230
		foreach ( $schema['properties'] as &$property ) {
231
			if ( isset( $property['arg_options'] ) ) {
232
				unset( $property['arg_options'] );
233
			}
234
		}
235
236
		return $schema;
237
	}
238
239
	/**
240
	 * Get the query params for collections.
241
	 *
242
	 * @return array
243
	 */
244
	public function get_collection_params() {
245
		return array(
246
			'context'                => $this->get_context_param(),
247
			'page'                   => array(
248
				'description'        => __( 'Current page of the collection.' ),
249
				'type'               => 'integer',
250
				'default'            => 1,
251
				'sanitize_callback'  => 'absint',
252
				'validate_callback'  => 'rest_validate_request_arg',
253
				'minimum'            => 1,
254
			),
255
			'per_page'               => array(
256
				'description'        => __( 'Maximum number of items to be returned in result set.' ),
257
				'type'               => 'integer',
258
				'default'            => 10,
259
				'minimum'            => 1,
260
				'maximum'            => 100,
261
				'sanitize_callback'  => 'absint',
262
				'validate_callback'  => 'rest_validate_request_arg',
263
			),
264
			'search'                 => array(
265
				'description'        => __( 'Limit results to those matching a string.' ),
266
				'type'               => 'string',
267
				'sanitize_callback'  => 'sanitize_text_field',
268
				'validate_callback'  => 'rest_validate_request_arg',
269
			),
270
		);
271
	}
272
273
	/**
274
	 * Get the magical context param.
275
	 *
276
	 * Ensures consistent description between endpoints, and populates enum from schema.
277
	 *
278
	 * @param array     $args
279
	 * @return array
280
	 */
281
	public function get_context_param( $args = array() ) {
282
		$param_details = array(
283
			'description'        => __( 'Scope under which the request is made; determines fields present in response.' ),
284
			'type'               => 'string',
285
			'sanitize_callback'  => 'sanitize_key',
286
			'validate_callback'  => 'rest_validate_request_arg',
287
		);
288
		$schema = $this->get_item_schema();
289
		if ( empty( $schema['properties'] ) ) {
290
			return array_merge( $param_details, $args );
291
		}
292
		$contexts = array();
293
		foreach ( $schema['properties'] as $attributes ) {
294
			if ( ! empty( $attributes['context'] ) ) {
295
				$contexts = array_merge( $contexts, $attributes['context'] );
296
			}
297
		}
298
		if ( ! empty( $contexts ) ) {
299
			$param_details['enum'] = array_unique( $contexts );
300
			rsort( $param_details['enum'] );
301
		}
302
		return array_merge( $param_details, $args );
303
	}
304
305
	/**
306
	 * Add the values from additional fields to a data object.
307
	 *
308
	 * @param array  $object
309
	 * @param WP_REST_Request $request
310
	 * @return array modified object with additional fields.
311
	 */
312
	protected function add_additional_fields_to_object( $object, $request ) {
313
314
		$additional_fields = $this->get_additional_fields();
315
316
		foreach ( $additional_fields as $field_name => $field_options ) {
317
318
			if ( ! $field_options['get_callback'] ) {
319
				continue;
320
			}
321
322
			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
323
		}
324
325
		return $object;
326
	}
327
328
	/**
329
	 * Update the values of additional fields added to a data object.
330
	 *
331
	 * @param array  $object
332
	 * @param WP_REST_Request $request
333
	 */
334
	protected function update_additional_fields_for_object( $object, $request ) {
335
336
		$additional_fields = $this->get_additional_fields();
337
338
		foreach ( $additional_fields as $field_name => $field_options ) {
339
340
			if ( ! $field_options['update_callback'] ) {
341
				continue;
342
			}
343
344
			// Don't run the update callbacks if the data wasn't passed in the request.
345
			if ( ! isset( $request[ $field_name ] ) ) {
346
				continue;
347
			}
348
349
			call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
350
		}
351
	}
352
353
	/**
354
	 * Add the schema from additional fields to an schema array.
355
	 *
356
	 * The type of object is inferred from the passed schema.
357
	 *
358
	 * @param array $schema Schema array.
359
	 */
360
	protected function add_additional_fields_schema( $schema ) {
361
		if ( empty( $schema['title'] ) ) {
362
			return $schema;
363
		}
364
365
		/**
366
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
367
		 */
368
		$object_type = $schema['title'];
369
370
		$additional_fields = $this->get_additional_fields( $object_type );
371
372
		foreach ( $additional_fields as $field_name => $field_options ) {
373
			if ( ! $field_options['schema'] ) {
374
				continue;
375
			}
376
377
			$schema['properties'][ $field_name ] = $field_options['schema'];
378
		}
379
380
		return $schema;
381
	}
382
383
	/**
384
	 * Get all the registered additional fields for a given object-type.
385
	 *
386
	 * @param  string $object_type
387
	 * @return array
388
	 */
389
	protected function get_additional_fields( $object_type = null ) {
390
391
		if ( ! $object_type ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object_type of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
392
			$object_type = $this->get_object_type();
393
		}
394
395
		if ( ! $object_type ) {
396
			return array();
397
		}
398
399
		global $wp_rest_additional_fields;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
400
401
		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
402
			return array();
403
		}
404
405
		return $wp_rest_additional_fields[ $object_type ];
406
	}
407
408
	/**
409
	 * Get the object type this controller is responsible for managing.
410
	 *
411
	 * @return string
412
	 */
413
	protected function get_object_type() {
414
		$schema = $this->get_item_schema();
415
416
		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...
417
			return null;
418
		}
419
420
		return $schema['title'];
421
	}
422
423
	/**
424
	 * Get an array of endpoint arguments from the item schema for the controller.
425
	 *
426
	 * @param string $method HTTP method of the request. The arguments
427
	 *                       for `CREATABLE` requests are checked for required
428
	 *                       values and may fall-back to a given default, this
429
	 *                       is not done on `EDITABLE` requests. Default is
430
	 *                       WP_REST_Server::CREATABLE.
431
	 * @return array $endpoint_args
432
	 */
433
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
434
435
		$schema                = $this->get_item_schema();
436
		$schema_properties     = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
437
		$endpoint_args = array();
438
439
		foreach ( $schema_properties as $field_id => $params ) {
440
441
			// Arguments specified as `readonly` are not allowed to be set.
442
			if ( ! empty( $params['readonly'] ) ) {
443
				continue;
444
			}
445
446
			$endpoint_args[ $field_id ] = array(
447
				'validate_callback' => 'rest_validate_request_arg',
448
				'sanitize_callback' => 'rest_sanitize_request_arg',
449
			);
450
451
			if ( isset( $params['description'] ) ) {
452
				$endpoint_args[ $field_id ]['description'] = $params['description'];
453
			}
454
455
			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
456
				$endpoint_args[ $field_id ]['default'] = $params['default'];
457
			}
458
459
			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
460
				$endpoint_args[ $field_id ]['required'] = true;
461
			}
462
463
			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
464
				if ( isset( $params[ $schema_prop ] ) ) {
465
					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
466
				}
467
			}
468
469
			// Merge in any options provided by the schema property.
470
			if ( isset( $params['arg_options'] ) ) {
471
472
				// Only use required / default from arg_options on CREATABLE endpoints.
473
				if ( WP_REST_Server::CREATABLE !== $method ) {
474
					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
475
				}
476
477
				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
478
			}
479
		}
480
481
		return $endpoint_args;
482
	}
483
484
	/**
485
	 * Retrieves post data given a post ID or post object.
486
	 *
487
	 * This is a subset of the functionality of the `get_post()` function, with
488
	 * the additional functionality of having `the_post` action done on the
489
	 * resultant post object. This is done so that plugins may manipulate the
490
	 * post that is used in the REST API.
491
	 *
492
	 * @see get_post()
493
	 * @global WP_Query $wp_query
494
	 *
495
	 * @param int|WP_Post $post Post ID or post object. Defaults to global $post.
496
	 * @return WP_Post|null A `WP_Post` object when successful.
497
	 */
498
	public function get_post( $post ) {
499
		$post_obj = get_post( $post );
500
501
		/**
502
		 * Filter the post.
503
		 *
504
		 * Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`.
505
		 *
506
		 * @param WP_Post|null $post_obj  The post object as returned by `get_post()`.
507
		 * @param int|WP_Post  $post      The original value used to obtain the post object.
508
		 */
509
		$post = apply_filters( 'rest_the_post', $post_obj, $post );
510
511
		return $post;
512
	}
513
}
514