Completed
Push — master ( f47a1d...37f03f )
by Claudio
28:06
created

WP_REST_Controller::get_context_param()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 23
rs 8.5906
nc 7
cc 5
eloc 17
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 10 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * @version 2.0-beta12
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', '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.", __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.", __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.", __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.", __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.", __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.", __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.", __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.", __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.", __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.", __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.", __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.", __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 ) ) {
1 ignored issue
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...
162
			return $response;
163
		}
164
165
		$data = (array) $response->get_data();
166
		$links = WP_REST_Server::get_response_links( $response );
167
		if ( ! empty( $links ) ) {
168
			$data['_links'] = $links;
169
		}
170
171
		return $data;
172
	}
173
174
	/**
175
	 * Filter a response based on the context defined in the schema.
176
	 *
177
	 * @param array $data
178
	 * @param string $context
179
	 * @return array
180
	 */
181
	public function filter_response_by_context( $data, $context ) {
182
183
		$schema = $this->get_item_schema();
184
		foreach ( $data as $key => $value ) {
185
			if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
186
				continue;
187
			}
188
189
			if ( ! in_array( $context, $schema['properties'][ $key ]['context'] ) ) {
190
				unset( $data[ $key ] );
191
			}
192
193
			if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
194
				foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
195
					if ( empty( $details['context'] ) ) {
196
						continue;
197
					}
198
					if ( ! in_array( $context, $details['context'] ) ) {
199
						if ( isset( $data[ $key ][ $attribute ] ) ) {
200
							unset( $data[ $key ][ $attribute ] );
201
						}
202
					}
203
				}
204
			}
205
		}
206
207
		return $data;
208
	}
209
210
	/**
211
	 * Get the item's schema, conforming to JSON Schema.
212
	 *
213
	 * @return array
214
	 */
215
	public function get_item_schema() {
216
		return $this->add_additional_fields_schema( array() );
217
	}
218
219
	/**
220
	 * Get the item's schema for display / public consumption purposes.
221
	 *
222
	 * @return array
223
	 */
224
	public function get_public_item_schema() {
225
226
		$schema = $this->get_item_schema();
227
228
		foreach ( $schema['properties'] as &$property ) {
229
			if ( isset( $property['arg_options'] ) ) {
230
				unset( $property['arg_options'] );
231
			}
232
		}
233
234
		return $schema;
235
	}
236
237
	/**
238
	 * Get the query params for collections.
239
	 *
240
	 * @return array
241
	 */
242
	public function get_collection_params() {
243
		return array(
244
			'context'                => $this->get_context_param(),
245
			'page'                   => array(
246
				'description'        => 'Current page of the collection.',
247
				'type'               => 'integer',
248
				'default'            => 1,
249
				'sanitize_callback'  => 'absint',
250
				'validate_callback'  => 'rest_validate_request_arg',
251
				'minimum'            => 1,
252
			),
253
			'per_page'               => array(
254
				'description'        => 'Maximum number of items to be returned in result set.',
255
				'type'               => 'integer',
256
				'default'            => 10,
257
				'minimum'            => 1,
258
				'maximum'            => 100,
259
				'sanitize_callback'  => 'absint',
260
				'validate_callback'  => 'rest_validate_request_arg',
261
			),
262
			'search'                 => array(
263
				'description'        => 'Limit results to those matching a string.',
264
				'type'               => 'string',
265
				'sanitize_callback'  => 'sanitize_text_field',
266
				'validate_callback'  => 'rest_validate_request_arg',
267
			),
268
		);
269
	}
270
271
	/**
272
	 * Get the magical context param.
273
	 *
274
	 * Ensures consistent description between endpoints, and populates enum from schema.
275
	 *
276
	 * @param array     $args
277
	 * @return array
278
	 */
279
	public function get_context_param( $args = array() ) {
280
		$param_details = array(
281
			'description'        => 'Scope under which the request is made; determines fields present in response.',
282
			'type'               => 'string',
283
			'sanitize_callback'  => 'sanitize_key',
284
			'validate_callback'  => 'rest_validate_request_arg',
285
		);
286
		$schema = $this->get_item_schema();
287
		if ( empty( $schema['properties'] ) ) {
288
			return array_merge( $param_details, $args );
289
		}
290
		$contexts = array();
291
		foreach ( $schema['properties'] as $key => $attributes ) {
292
			if ( ! empty( $attributes['context'] ) ) {
293
				$contexts = array_merge( $contexts, $attributes['context'] );
294
			}
295
		}
296
		if ( ! empty( $contexts ) ) {
297
			$param_details['enum'] = array_unique( $contexts );
298
			rsort( $param_details['enum'] );
299
		}
300
		return array_merge( $param_details, $args );
301
	}
302
303
	/**
304
	 * Add the values from additional fields to a data object.
305
	 *
306
	 * @param array  $object
307
	 * @param WP_REST_Request $request
308
	 * @return array modified object with additional fields.
309
	 */
310
	protected function add_additional_fields_to_object( $object, $request ) {
311
312
		$additional_fields = $this->get_additional_fields();
313
314
		foreach ( $additional_fields as $field_name => $field_options ) {
315
316
			if ( ! $field_options['get_callback'] ) {
317
				continue;
318
			}
319
320
			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
321
		}
322
323
		return $object;
324
	}
325
326
	/**
327
	 * Update the values of additional fields added to a data object.
328
	 *
329
	 * @param array  $object
330
	 * @param WP_REST_Request $request
331
	 */
332
	protected function update_additional_fields_for_object( $object, $request ) {
333
334
		$additional_fields = $this->get_additional_fields();
335
336
		foreach ( $additional_fields as $field_name => $field_options ) {
337
338
			if ( ! $field_options['update_callback'] ) {
339
				continue;
340
			}
341
342
			// Don't run the update callbacks if the data wasn't passed in the request.
343
			if ( ! isset( $request[ $field_name ] ) ) {
344
				continue;
345
			}
346
347
			call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
348
		}
349
	}
350
351
	/**
352
	 * Add the schema from additional fields to an schema array.
353
	 *
354
	 * The type of object is inferred from the passed schema.
355
	 *
356
	 * @param array $schema Schema array.
357
	 */
358
	protected function add_additional_fields_schema( $schema ) {
359
		if ( empty( $schema['title'] ) ) {
360
			return $schema;
361
		}
362
363
		/**
364
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
365
		 */
366
		$object_type = $schema['title'];
367
368
		$additional_fields = $this->get_additional_fields( $object_type );
369
370
		foreach ( $additional_fields as $field_name => $field_options ) {
371
			if ( ! $field_options['schema'] ) {
372
				continue;
373
			}
374
375
			$schema['properties'][ $field_name ] = $field_options['schema'];
376
		}
377
378
		return $schema;
379
	}
380
381
	/**
382
	 * Get all the registered additional fields for a given object-type.
383
	 *
384
	 * @param  string $object_type
385
	 * @return array
386
	 */
387
	protected function get_additional_fields( $object_type = null ) {
388
389
		if ( ! $object_type ) {
1 ignored issue
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...
390
			$object_type = $this->get_object_type();
391
		}
392
393
		if ( ! $object_type ) {
394
			return array();
395
		}
396
397
		global $wp_rest_additional_fields;
398
399
		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
400
			return array();
401
		}
402
403
		return $wp_rest_additional_fields[ $object_type ];
404
	}
405
406
	/**
407
	 * Get the object type this controller is responsible for managing.
408
	 *
409
	 * @return string
410
	 */
411
	protected function get_object_type() {
412
		$schema = $this->get_item_schema();
413
414
		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...
415
			return null;
416
		}
417
418
		return $schema['title'];
419
	}
420
421
	/**
422
	 * Get an array of endpoint arguments from the item schema for the controller.
423
	 *
424
	 * @param string $method HTTP method of the request. The arguments
425
	 *                       for `CREATABLE` requests are checked for required
426
	 *                       values and may fall-back to a given default, this
427
	 *                       is not done on `EDITABLE` requests. Default is
428
	 *                       WP_REST_Server::CREATABLE.
429
	 * @return array $endpoint_args
430
	 */
431
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
432
433
		$schema                = $this->get_item_schema();
434
		$schema_properties     = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
435
		$endpoint_args = array();
436
437
		foreach ( $schema_properties as $field_id => $params ) {
438
439
			// Arguments specified as `readonly` are not allowed to be set.
440
			if ( ! empty( $params['readonly'] ) ) {
441
				continue;
442
			}
443
444
			$endpoint_args[ $field_id ] = array(
445
				'validate_callback' => 'rest_validate_request_arg',
446
				'sanitize_callback' => 'rest_sanitize_request_arg',
447
			);
448
449
			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
450
				$endpoint_args[ $field_id ]['default'] = $params['default'];
451
			}
452
453
			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
454
				$endpoint_args[ $field_id ]['required'] = true;
455
			}
456
457
			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
458
				if ( isset( $params[ $schema_prop ] ) ) {
459
					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
460
				}
461
			}
462
463
			// Merge in any options provided by the schema property.
464
			if ( isset( $params['arg_options'] ) ) {
465
466
				// Only use required / default from arg_options on CREATABLE endpoints.
467
				if ( WP_REST_Server::CREATABLE !== $method ) {
468
					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
469
				}
470
471
				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
472
			}
473
		}
474
475
		return $endpoint_args;
476
	}
477
478
}
479