Completed
Branch develop (4d5230)
by
unknown
02:35
created

WP_REST_Controller::sanitize_slug()   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 {
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
				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
	 * @return bool|WP_Error True on success, WP_Error object if a field cannot be updated.
334
	 */
335
	protected function update_additional_fields_for_object( $object, $request ) {
336
		$additional_fields = $this->get_additional_fields();
337
338
		foreach ( $additional_fields as $field_name => $field_options ) {
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
			$result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
349
			if ( is_wp_error( $result ) ) {
350
				return $result;
351
			}
352
		}
353
354
		return true;
355
	}
356
357
	/**
358
	 * Add the schema from additional fields to an schema array.
359
	 *
360
	 * The type of object is inferred from the passed schema.
361
	 *
362
	 * @param array $schema Schema array.
363
	 */
364
	protected function add_additional_fields_schema( $schema ) {
365
		if ( empty( $schema['title'] ) ) {
366
			return $schema;
367
		}
368
369
		/**
370
		 * Can't use $this->get_object_type otherwise we cause an inf loop.
371
		 */
372
		$object_type = $schema['title'];
373
374
		$additional_fields = $this->get_additional_fields( $object_type );
375
376
		foreach ( $additional_fields as $field_name => $field_options ) {
377
			if ( ! $field_options['schema'] ) {
378
				continue;
379
			}
380
381
			$schema['properties'][ $field_name ] = $field_options['schema'];
382
		}
383
384
		return $schema;
385
	}
386
387
	/**
388
	 * Get all the registered additional fields for a given object-type.
389
	 *
390
	 * @param  string $object_type
391
	 * @return array
392
	 */
393
	protected function get_additional_fields( $object_type = null ) {
394
395
		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...
396
			$object_type = $this->get_object_type();
397
		}
398
399
		if ( ! $object_type ) {
400
			return array();
401
		}
402
403
		global $wp_rest_additional_fields;
404
405
		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
406
			return array();
407
		}
408
409
		return $wp_rest_additional_fields[ $object_type ];
410
	}
411
412
	/**
413
	 * Get the object type this controller is responsible for managing.
414
	 *
415
	 * @return string
416
	 */
417
	protected function get_object_type() {
418
		$schema = $this->get_item_schema();
419
420
		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...
421
			return null;
422
		}
423
424
		return $schema['title'];
425
	}
426
427
	/**
428
	 * Get an array of endpoint arguments from the item schema for the controller.
429
	 *
430
	 * @param string $method HTTP method of the request. The arguments
431
	 *                       for `CREATABLE` requests are checked for required
432
	 *                       values and may fall-back to a given default, this
433
	 *                       is not done on `EDITABLE` requests. Default is
434
	 *                       WP_REST_Server::CREATABLE.
435
	 * @return array $endpoint_args
436
	 */
437
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
438
439
		$schema                = $this->get_item_schema();
440
		$schema_properties     = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
441
		$endpoint_args = array();
442
443
		foreach ( $schema_properties as $field_id => $params ) {
444
445
			// Arguments specified as `readonly` are not allowed to be set.
446
			if ( ! empty( $params['readonly'] ) ) {
447
				continue;
448
			}
449
450
			$endpoint_args[ $field_id ] = array(
451
				'validate_callback' => 'rest_validate_request_arg',
452
				'sanitize_callback' => 'rest_sanitize_request_arg',
453
			);
454
455
			if ( isset( $params['description'] ) ) {
456
				$endpoint_args[ $field_id ]['description'] = $params['description'];
457
			}
458
459
			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
460
				$endpoint_args[ $field_id ]['default'] = $params['default'];
461
			}
462
463
			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
464
				$endpoint_args[ $field_id ]['required'] = true;
465
			}
466
467
			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
468
				if ( isset( $params[ $schema_prop ] ) ) {
469
					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
470
				}
471
			}
472
473
			// Merge in any options provided by the schema property.
474
			if ( isset( $params['arg_options'] ) ) {
475
476
				// Only use required / default from arg_options on CREATABLE endpoints.
477
				if ( WP_REST_Server::CREATABLE !== $method ) {
478
					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
479
				}
480
481
				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
482
			}
483
		}
484
485
		return $endpoint_args;
486
	}
487
488
	/**
489
	 * Retrieves post data given a post ID or post object.
490
	 *
491
	 * This is a subset of the functionality of the `get_post()` function, with
492
	 * the additional functionality of having `the_post` action done on the
493
	 * resultant post object. This is done so that plugins may manipulate the
494
	 * post that is used in the REST API.
495
	 *
496
	 * @see get_post()
497
	 * @global WP_Query $wp_query
498
	 *
499
	 * @param int|WP_Post $post Post ID or post object. Defaults to global $post.
500
	 * @return WP_Post|null A `WP_Post` object when successful.
501
	 */
502
	public function get_post( $post ) {
503
		$post_obj = get_post( $post );
504
505
		/**
506
		 * Filter the post.
507
		 *
508
		 * Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`.
509
		 *
510
		 * @param WP_Post|null $post_obj  The post object as returned by `get_post()`.
511
		 * @param int|WP_Post  $post      The original value used to obtain the post object.
512
		 */
513
		$post = apply_filters( 'rest_the_post', $post_obj, $post );
514
515
		return $post;
516
	}
517
518
	/**
519
	 * Sanitize the slug value.
520
	 *
521
	 * @internal We can't use {@see sanitize_title} directly, as the second
522
	 * parameter is the fallback title, which would end up being set to the
523
	 * request object.
524
	 * @see https://github.com/WP-API/WP-API/issues/1585
525
	 *
526
	 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
527
	 *
528
	 * @param string $slug Slug value passed in request.
529
	 * @return string Sanitized value for the slug.
530
	 */
531
	public function sanitize_slug( $slug ) {
532
		return sanitize_title( $slug );
533
	}
534
}
535