Completed
Branch develop (41d184)
by
unknown
02:35
created

WP_REST_Controller::get_post()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 15
rs 9.4285
1
<?php
2
3
4
abstract class WP_REST_Controller {
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 ) {
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 ) {
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 ) {
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 ) {
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 ) {
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 ) {
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 ) {
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 ) {
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
			}
193
194
			if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
195
				foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
196
					if ( empty( $details['context'] ) ) {
197
						continue;
198
					}
199
					if ( ! in_array( $context, $details['context'] ) ) {
200
						if ( isset( $data[ $key ][ $attribute ] ) ) {
201
							unset( $data[ $key ][ $attribute ] );
202
						}
203
					}
204
				}
205
			}
206
		}
207
208
		return $data;
209
	}
210
211
	/**
212
	 * Get the item's schema, conforming to JSON Schema.
213
	 *
214
	 * @return array
215
	 */
216
	public function get_item_schema() {
217
		return $this->add_additional_fields_schema( array() );
218
	}
219
220
	/**
221
	 * Get the item's schema for display / public consumption purposes.
222
	 *
223
	 * @return array
224
	 */
225
	public function get_public_item_schema() {
226
227
		$schema = $this->get_item_schema();
228
229
		foreach ( $schema['properties'] as &$property ) {
230
			if ( isset( $property['arg_options'] ) ) {
231
				unset( $property['arg_options'] );
232
			}
233
		}
234
235
		return $schema;
236
	}
237
238
	/**
239
	 * Get the query params for collections.
240
	 *
241
	 * @return array
242
	 */
243
	public function get_collection_params() {
244
		return array(
245
			'context'                => $this->get_context_param(),
246
			'page'                   => array(
247
				'description'        => __( 'Current page of the collection.' ),
248
				'type'               => 'integer',
249
				'default'            => 1,
250
				'sanitize_callback'  => 'absint',
251
				'validate_callback'  => 'rest_validate_request_arg',
252
				'minimum'            => 1,
253
			),
254
			'per_page'               => array(
255
				'description'        => __( 'Maximum number of items to be returned in result set.' ),
256
				'type'               => 'integer',
257
				'default'            => 10,
258
				'minimum'            => 1,
259
				'maximum'            => 100,
260
				'sanitize_callback'  => 'absint',
261
				'validate_callback'  => 'rest_validate_request_arg',
262
			),
263
			'search'                 => array(
264
				'description'        => __( 'Limit results to those matching a string.' ),
265
				'type'               => 'string',
266
				'sanitize_callback'  => 'sanitize_text_field',
267
				'validate_callback'  => 'rest_validate_request_arg',
268
			),
269
		);
270
	}
271
272
	/**
273
	 * Get the magical context param.
274
	 *
275
	 * Ensures consistent description between endpoints, and populates enum from schema.
276
	 *
277
	 * @param array     $args
278
	 * @return array
279
	 */
280
	public function get_context_param( $args = array() ) {
281
		$param_details = array(
282
			'description'        => __( 'Scope under which the request is made; determines fields present in response.' ),
283
			'type'               => 'string',
284
			'sanitize_callback'  => 'sanitize_key',
285
			'validate_callback'  => 'rest_validate_request_arg',
286
		);
287
		$schema = $this->get_item_schema();
288
		if ( empty( $schema['properties'] ) ) {
289
			return array_merge( $param_details, $args );
290
		}
291
		$contexts = array();
292
		foreach ( $schema['properties'] as $attributes ) {
293
			if ( ! empty( $attributes['context'] ) ) {
294
				$contexts = array_merge( $contexts, $attributes['context'] );
295
			}
296
		}
297
		if ( ! empty( $contexts ) ) {
298
			$param_details['enum'] = array_unique( $contexts );
299
			rsort( $param_details['enum'] );
300
		}
301
		return array_merge( $param_details, $args );
302
	}
303
304
	/**
305
	 * Add the values from additional fields to a data object.
306
	 *
307
	 * @param array  $object
308
	 * @param WP_REST_Request $request
309
	 * @return array modified object with additional fields.
310
	 */
311
	protected function add_additional_fields_to_object( $object, $request ) {
312
313
		$additional_fields = $this->get_additional_fields();
314
315
		foreach ( $additional_fields as $field_name => $field_options ) {
316
317
			if ( ! $field_options['get_callback'] ) {
318
				continue;
319
			}
320
321
			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
322
		}
323
324
		return $object;
325
	}
326
327
	/**
328
	 * Update the values of additional fields added to a data object.
329
	 *
330
	 * @param array  $object
331
	 * @param WP_REST_Request $request
332
	 */
333
	protected function update_additional_fields_for_object( $object, $request ) {
334
335
		$additional_fields = $this->get_additional_fields();
336
337
		foreach ( $additional_fields as $field_name => $field_options ) {
338
339
			if ( ! $field_options['update_callback'] ) {
340
				continue;
341
			}
342
343
			// Don't run the update callbacks if the data wasn't passed in the request.
344
			if ( ! isset( $request[ $field_name ] ) ) {
345
				continue;
346
			}
347
348
			call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
349
		}
350
	}
351
352
	/**
353
	 * Add the schema from additional fields to an schema array.
354
	 *
355
	 * The type of object is inferred from the passed schema.
356
	 *
357
	 * @param array $schema Schema array.
358
	 */
359
	protected function add_additional_fields_schema( $schema ) {
360
		if ( empty( $schema['title'] ) ) {
361
			return $schema;
362
		}
363
364
		/**
365
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
366
		 */
367
		$object_type = $schema['title'];
368
369
		$additional_fields = $this->get_additional_fields( $object_type );
370
371
		foreach ( $additional_fields as $field_name => $field_options ) {
372
			if ( ! $field_options['schema'] ) {
373
				continue;
374
			}
375
376
			$schema['properties'][ $field_name ] = $field_options['schema'];
377
		}
378
379
		return $schema;
380
	}
381
382
	/**
383
	 * Get all the registered additional fields for a given object-type.
384
	 *
385
	 * @param  string $object_type
386
	 * @return array
387
	 */
388
	protected function get_additional_fields( $object_type = null ) {
389
390
		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...
391
			$object_type = $this->get_object_type();
392
		}
393
394
		if ( ! $object_type ) {
395
			return array();
396
		}
397
398
		global $wp_rest_additional_fields;
399
400
		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
401
			return array();
402
		}
403
404
		return $wp_rest_additional_fields[ $object_type ];
405
	}
406
407
	/**
408
	 * Get the object type this controller is responsible for managing.
409
	 *
410
	 * @return string
411
	 */
412
	protected function get_object_type() {
413
		$schema = $this->get_item_schema();
414
415
		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...
416
			return null;
417
		}
418
419
		return $schema['title'];
420
	}
421
422
	/**
423
	 * Get an array of endpoint arguments from the item schema for the controller.
424
	 *
425
	 * @param string $method HTTP method of the request. The arguments
426
	 *                       for `CREATABLE` requests are checked for required
427
	 *                       values and may fall-back to a given default, this
428
	 *                       is not done on `EDITABLE` requests. Default is
429
	 *                       WP_REST_Server::CREATABLE.
430
	 * @return array $endpoint_args
431
	 */
432
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
433
434
		$schema                = $this->get_item_schema();
435
		$schema_properties     = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
436
		$endpoint_args = array();
437
438
		foreach ( $schema_properties as $field_id => $params ) {
439
440
			// Arguments specified as `readonly` are not allowed to be set.
441
			if ( ! empty( $params['readonly'] ) ) {
442
				continue;
443
			}
444
445
			$endpoint_args[ $field_id ] = array(
446
				'validate_callback' => 'rest_validate_request_arg',
447
				'sanitize_callback' => 'rest_sanitize_request_arg',
448
			);
449
450
			if ( isset( $params['description'] ) ) {
451
				$endpoint_args[ $field_id ]['description'] = $params['description'];
452
			}
453
454
			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
455
				$endpoint_args[ $field_id ]['default'] = $params['default'];
456
			}
457
458
			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
459
				$endpoint_args[ $field_id ]['required'] = true;
460
			}
461
462
			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
463
				if ( isset( $params[ $schema_prop ] ) ) {
464
					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
465
				}
466
			}
467
468
			// Merge in any options provided by the schema property.
469
			if ( isset( $params['arg_options'] ) ) {
470
471
				// Only use required / default from arg_options on CREATABLE endpoints.
472
				if ( WP_REST_Server::CREATABLE !== $method ) {
473
					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
474
				}
475
476
				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
477
			}
478
		}
479
480
		return $endpoint_args;
481
	}
482
483
	/**
484
	 * Retrieves post data given a post ID or post object.
485
	 *
486
	 * This is a subset of the functionality of the `get_post()` function, with
487
	 * the additional functionality of having `the_post` action done on the
488
	 * resultant post object. This is done so that plugins may manipulate the
489
	 * post that is used in the REST API.
490
	 *
491
	 * @see get_post()
492
	 * @global WP_Query $wp_query
493
	 *
494
	 * @param int|WP_Post $post Post ID or post object. Defaults to global $post.
495
	 * @return WP_Post|null A `WP_Post` object when successful.
496
	 */
497
	public function get_post( $post ) {
498
		$post_obj = get_post( $post );
499
500
		/**
501
		 * Filter the post.
502
		 *
503
		 * Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`.
504
		 *
505
		 * @param WP_Post|null $post_obj  The post object as returned by `get_post()`.
506
		 * @param int|WP_Post  $post      The original value used to obtain the post object.
507
		 */
508
		$post = apply_filters( 'rest_the_post', $post_obj, $post );
509
510
		return $post;
511
	}
512
}
513