Completed
Push — master ( a7cd2a...eabd6c )
by Stephen
38:42
created

WP_REST_Request::offsetExists()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
/**
3
 * REST API: WP_REST_Request class
4
 *
5
 * @package WordPress
6
 * @subpackage REST_API
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used to implement a REST request object.
12
 *
13
 * Contains data from the request, to be passed to the callback.
14
 *
15
 * Note: This implements ArrayAccess, and acts as an array of parameters when
16
 * used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
17
 * so be aware it may have non-array behaviour in some cases.
18
 *
19
 * Note: When using features provided by ArrayAccess, be aware that WordPress deliberately
20
 * does not distinguish between arguments of the same name for different request methods.
21
 * For instance, in a request with `GET id=1` and `POST id=2`, `$request['id']` will equal
22
 * 2 (`POST`) not 1 (`GET`). For more precision between request methods, use
23
 * {@see WP_REST_Request::get_body_params()}, {@see WP_REST_Request::get_url_params()},
24
 * etc.
25
 *
26
 * @since 4.4.0
27
 *
28
 * @see ArrayAccess
29
 */
30
class WP_REST_Request implements ArrayAccess {
31
32
	/**
33
	 * HTTP method.
34
	 *
35
	 * @since 4.4.0
36
	 * @access protected
37
	 * @var string
38
	 */
39
	protected $method = '';
40
41
	/**
42
	 * Parameters passed to the request.
43
	 *
44
	 * These typically come from the `$_GET`, `$_POST` and `$_FILES`
45
	 * superglobals when being created from the global scope.
46
	 *
47
	 * @since 4.4.0
48
	 * @access protected
49
	 * @var array Contains GET, POST and FILES keys mapping to arrays of data.
50
	 */
51
	protected $params;
52
53
	/**
54
	 * HTTP headers for the request.
55
	 *
56
	 * @since 4.4.0
57
	 * @access protected
58
	 * @var array Map of key to value. Key is always lowercase, as per HTTP specification.
59
	 */
60
	protected $headers = array();
61
62
	/**
63
	 * Body data.
64
	 *
65
	 * @since 4.4.0
66
	 * @access protected
67
	 * @var string Binary data from the request.
68
	 */
69
	protected $body = null;
70
71
	/**
72
	 * Route matched for the request.
73
	 *
74
	 * @since 4.4.0
75
	 * @access protected
76
	 * @var string
77
	 */
78
	protected $route;
79
80
	/**
81
	 * Attributes (options) for the route that was matched.
82
	 *
83
	 * This is the options array used when the route was registered, typically
84
	 * containing the callback as well as the valid methods for the route.
85
	 *
86
	 * @since 4.4.0
87
	 * @access protected
88
	 * @var array Attributes for the request.
89
	 */
90
	protected $attributes = array();
91
92
	/**
93
	 * Used to determine if the JSON data has been parsed yet.
94
	 *
95
	 * Allows lazy-parsing of JSON data where possible.
96
	 *
97
	 * @since 4.4.0
98
	 * @access protected
99
	 * @var bool
100
	 */
101
	protected $parsed_json = false;
102
103
	/**
104
	 * Used to determine if the body data has been parsed yet.
105
	 *
106
	 * @since 4.4.0
107
	 * @access protected
108
	 * @var bool
109
	 */
110
	protected $parsed_body = false;
111
112
	/**
113
	 * Constructor.
114
	 *
115
	 * @since 4.4.0
116
	 * @access public
117
	 *
118
	 * @param string $method     Optional. Request method. Default empty.
119
	 * @param string $route      Optional. Request route. Default empty.
120
	 * @param array  $attributes Optional. Request attributes. Default empty array.
121
	 */
122
	public function __construct( $method = '', $route = '', $attributes = array() ) {
123
		$this->params = array(
124
			'URL'   => array(),
125
			'GET'   => array(),
126
			'POST'  => array(),
127
			'FILES' => array(),
128
129
			// See parse_json_params.
130
			'JSON'  => null,
131
132
			'defaults' => array(),
133
		);
134
135
		$this->set_method( $method );
136
		$this->set_route( $route );
137
		$this->set_attributes( $attributes );
138
	}
139
140
	/**
141
	 * Retrieves the HTTP method for the request.
142
	 *
143
	 * @since 4.4.0
144
	 * @access public
145
	 *
146
	 * @return string HTTP method.
147
	 */
148
	public function get_method() {
149
		return $this->method;
150
	}
151
152
	/**
153
	 * Sets HTTP method for the request.
154
	 *
155
	 * @since 4.4.0
156
	 * @access public
157
	 *
158
	 * @param string $method HTTP method.
159
	 */
160
	public function set_method( $method ) {
161
		$this->method = strtoupper( $method );
162
	}
163
164
	/**
165
	 * Retrieves all headers from the request.
166
	 *
167
	 * @since 4.4.0
168
	 * @access public
169
	 *
170
	 * @return array Map of key to value. Key is always lowercase, as per HTTP specification.
171
	 */
172
	public function get_headers() {
173
		return $this->headers;
174
	}
175
176
	/**
177
	 * Canonicalizes the header name.
178
	 *
179
	 * Ensures that header names are always treated the same regardless of
180
	 * source. Header names are always case insensitive.
181
	 *
182
	 * Note that we treat `-` (dashes) and `_` (underscores) as the same
183
	 * character, as per header parsing rules in both Apache and nginx.
184
	 *
185
	 * @link http://stackoverflow.com/q/18185366
186
	 * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
187
	 * @link http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
188
	 *
189
	 * @since 4.4.0
190
	 * @access public
191
	 * @static
192
	 *
193
	 * @param string $key Header name.
194
	 * @return string Canonicalized name.
195
	 */
196
	public static function canonicalize_header_name( $key ) {
197
		$key = strtolower( $key );
198
		$key = str_replace( '-', '_', $key );
199
200
		return $key;
201
	}
202
203
	/**
204
	 * Retrieves the given header from the request.
205
	 *
206
	 * If the header has multiple values, they will be concatenated with a comma
207
	 * as per the HTTP specification. Be aware that some non-compliant headers
208
	 * (notably cookie headers) cannot be joined this way.
209
	 *
210
	 * @since 4.4.0
211
	 * @access public
212
	 *
213
	 * @param string $key Header name, will be canonicalized to lowercase.
214
	 * @return string|null String value if set, null otherwise.
215
	 */
216 View Code Duplication
	public function get_header( $key ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
217
		$key = $this->canonicalize_header_name( $key );
218
219
		if ( ! isset( $this->headers[ $key ] ) ) {
220
			return null;
221
		}
222
223
		return implode( ',', $this->headers[ $key ] );
224
	}
225
226
	/**
227
	 * Retrieves header values from the request.
228
	 *
229
	 * @since 4.4.0
230
	 * @access public
231
	 *
232
	 * @param string $key Header name, will be canonicalized to lowercase.
233
	 * @return array|null List of string values if set, null otherwise.
234
	 */
235 View Code Duplication
	public function get_header_as_array( $key ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
236
		$key = $this->canonicalize_header_name( $key );
237
238
		if ( ! isset( $this->headers[ $key ] ) ) {
239
			return null;
240
		}
241
242
		return $this->headers[ $key ];
243
	}
244
245
	/**
246
	 * Sets the header on request.
247
	 *
248
	 * @since 4.4.0
249
	 * @access public
250
	 *
251
	 * @param string $key   Header name.
252
	 * @param string $value Header value, or list of values.
253
	 */
254
	public function set_header( $key, $value ) {
255
		$key = $this->canonicalize_header_name( $key );
256
		$value = (array) $value;
257
258
		$this->headers[ $key ] = $value;
259
	}
260
261
	/**
262
	 * Appends a header value for the given header.
263
	 *
264
	 * @since 4.4.0
265
	 * @access public
266
	 *
267
	 * @param string $key   Header name.
268
	 * @param string $value Header value, or list of values.
269
	 */
270
	public function add_header( $key, $value ) {
271
		$key = $this->canonicalize_header_name( $key );
272
		$value = (array) $value;
273
274
		if ( ! isset( $this->headers[ $key ] ) ) {
275
			$this->headers[ $key ] = array();
276
		}
277
278
		$this->headers[ $key ] = array_merge( $this->headers[ $key ], $value );
279
	}
280
281
	/**
282
	 * Removes all values for a header.
283
	 *
284
	 * @since 4.4.0
285
	 * @access public
286
	 *
287
	 * @param string $key Header name.
288
	 */
289
	public function remove_header( $key ) {
290
		unset( $this->headers[ $key ] );
291
	}
292
293
	/**
294
	 * Sets headers on the request.
295
	 *
296
	 * @since 4.4.0
297
	 * @access public
298
	 *
299
	 * @param array $headers  Map of header name to value.
300
	 * @param bool  $override If true, replace the request's headers. Otherwise, merge with existing.
301
	 */
302
	public function set_headers( $headers, $override = true ) {
303
		if ( true === $override ) {
304
			$this->headers = array();
305
		}
306
307
		foreach ( $headers as $key => $value ) {
308
			$this->set_header( $key, $value );
309
		}
310
	}
311
312
	/**
313
	 * Retrieves the content-type of the request.
314
	 *
315
	 * @since 4.4.0
316
	 * @access public
317
	 *
318
	 * @return array Map containing 'value' and 'parameters' keys.
319
	 */
320
	public function get_content_type() {
321
		$value = $this->get_header( 'content-type' );
322
		if ( empty( $value ) ) {
323
			return null;
324
		}
325
326
		$parameters = '';
327
		if ( strpos( $value, ';' ) ) {
328
			list( $value, $parameters ) = explode( ';', $value, 2 );
329
		}
330
331
		$value = strtolower( $value );
332
		if ( strpos( $value, '/' ) === false ) {
333
			return null;
334
		}
335
336
		// Parse type and subtype out.
337
		list( $type, $subtype ) = explode( '/', $value, 2 );
338
339
		$data = compact( 'value', 'type', 'subtype', 'parameters' );
340
		$data = array_map( 'trim', $data );
341
342
		return $data;
343
	}
344
345
	/**
346
	 * Retrieves the parameter priority order.
347
	 *
348
	 * Used when checking parameters in get_param().
349
	 *
350
	 * @since 4.4.0
351
	 * @access protected
352
	 *
353
	 * @return array List of types to check, in order of priority.
354
	 */
355
	protected function get_parameter_order() {
356
		$order = array();
357
		$order[] = 'JSON';
358
359
		$this->parse_json_params();
360
361
		// Ensure we parse the body data.
362
		$body = $this->get_body();
363
		if ( $this->method !== 'POST' && ! empty( $body ) ) {
364
			$this->parse_body_params();
365
		}
366
367
		$accepts_body_data = array( 'POST', 'PUT', 'PATCH' );
368
		if ( in_array( $this->method, $accepts_body_data ) ) {
369
			$order[] = 'POST';
370
		}
371
372
		$order[] = 'GET';
373
		$order[] = 'URL';
374
		$order[] = 'defaults';
375
376
		/**
377
		 * Filter the parameter order.
378
		 *
379
		 * The order affects which parameters are checked when using get_param() and family.
380
		 * This acts similarly to PHP's `request_order` setting.
381
		 *
382
		 * @since 4.4.0
383
		 *
384
		 * @param array           $order {
385
		 *    An array of types to check, in order of priority.
386
		 *
387
		 *    @param string $type The type to check.
388
		 * }
389
		 * @param WP_REST_Request $this The request object.
390
		 */
391
		return apply_filters( 'rest_request_parameter_order', $order, $this );
392
	}
393
394
	/**
395
	 * Retrieves a parameter from the request.
396
	 *
397
	 * @since 4.4.0
398
	 * @access public
399
	 *
400
	 * @param string $key Parameter name.
401
	 * @return mixed|null Value if set, null otherwise.
402
	 */
403
	public function get_param( $key ) {
404
		$order = $this->get_parameter_order();
405
406
		foreach ( $order as $type ) {
407
			// Determine if we have the parameter for this type.
408
			if ( isset( $this->params[ $type ][ $key ] ) ) {
409
				return $this->params[ $type ][ $key ];
410
			}
411
		}
412
413
		return null;
414
	}
415
416
	/**
417
	 * Sets a parameter on the request.
418
	 *
419
	 * @since 4.4.0
420
	 * @access public
421
	 *
422
	 * @param string $key   Parameter name.
423
	 * @param mixed  $value Parameter value.
424
	 */
425
	public function set_param( $key, $value ) {
426
		switch ( $this->method ) {
427
			case 'POST':
428
				$this->params['POST'][ $key ] = $value;
429
				break;
430
431
			default:
432
				$this->params['GET'][ $key ] = $value;
433
				break;
434
		}
435
	}
436
437
	/**
438
	 * Retrieves merged parameters from the request.
439
	 *
440
	 * The equivalent of get_param(), but returns all parameters for the request.
441
	 * Handles merging all the available values into a single array.
442
	 *
443
	 * @since 4.4.0
444
	 * @access public
445
	 *
446
	 * @return array Map of key to value.
447
	 */
448
	public function get_params() {
449
		$order = $this->get_parameter_order();
450
		$order = array_reverse( $order, true );
451
452
		$params = array();
453
		foreach ( $order as $type ) {
454
			$params = array_merge( $params, (array) $this->params[ $type ] );
455
		}
456
457
		return $params;
458
	}
459
460
	/**
461
	 * Retrieves parameters from the route itself.
462
	 *
463
	 * These are parsed from the URL using the regex.
464
	 *
465
	 * @since 4.4.0
466
	 * @access public
467
	 *
468
	 * @return array Parameter map of key to value.
469
	 */
470
	public function get_url_params() {
471
		return $this->params['URL'];
472
	}
473
474
	/**
475
	 * Sets parameters from the route.
476
	 *
477
	 * Typically, this is set after parsing the URL.
478
	 *
479
	 * @since 4.4.0
480
	 * @access public
481
	 *
482
	 * @param array $params Parameter map of key to value.
483
	 */
484
	public function set_url_params( $params ) {
485
		$this->params['URL'] = $params;
486
	}
487
488
	/**
489
	 * Retrieves parameters from the query string.
490
	 *
491
	 * These are the parameters you'd typically find in `$_GET`.
492
	 *
493
	 * @since 4.4.0
494
	 * @access public
495
	 *
496
	 * @return array Parameter map of key to value
497
	 */
498
	public function get_query_params() {
499
		return $this->params['GET'];
500
	}
501
502
	/**
503
	 * Sets parameters from the query string.
504
	 *
505
	 * Typically, this is set from `$_GET`.
506
	 *
507
	 * @since 4.4.0
508
	 * @access public
509
	 *
510
	 * @param array $params Parameter map of key to value.
511
	 */
512
	public function set_query_params( $params ) {
513
		$this->params['GET'] = $params;
514
	}
515
516
	/**
517
	 * Retrieves parameters from the body.
518
	 *
519
	 * These are the parameters you'd typically find in `$_POST`.
520
	 *
521
	 * @since 4.4.0
522
	 * @access public
523
	 *
524
	 * @return array Parameter map of key to value.
525
	 */
526
	public function get_body_params() {
527
		return $this->params['POST'];
528
	}
529
530
	/**
531
	 * Sets parameters from the body.
532
	 *
533
	 * Typically, this is set from `$_POST`.
534
	 *
535
	 * @since 4.4.0
536
	 * @access public
537
	 *
538
	 * @param array $params Parameter map of key to value.
539
	 */
540
	public function set_body_params( $params ) {
541
		$this->params['POST'] = $params;
542
	}
543
544
	/**
545
	 * Retrieves multipart file parameters from the body.
546
	 *
547
	 * These are the parameters you'd typically find in `$_FILES`.
548
	 *
549
	 * @since 4.4.0
550
	 * @access public
551
	 *
552
	 * @return array Parameter map of key to value
553
	 */
554
	public function get_file_params() {
555
		return $this->params['FILES'];
556
	}
557
558
	/**
559
	 * Sets multipart file parameters from the body.
560
	 *
561
	 * Typically, this is set from `$_FILES`.
562
	 *
563
	 * @since 4.4.0
564
	 * @access public
565
	 *
566
	 * @param array $params Parameter map of key to value.
567
	 */
568
	public function set_file_params( $params ) {
569
		$this->params['FILES'] = $params;
570
	}
571
572
	/**
573
	 * Retrieves the default parameters.
574
	 *
575
	 * These are the parameters set in the route registration.
576
	 *
577
	 * @since 4.4.0
578
	 * @access public
579
	 *
580
	 * @return array Parameter map of key to value
581
	 */
582
	public function get_default_params() {
583
		return $this->params['defaults'];
584
	}
585
586
	/**
587
	 * Sets default parameters.
588
	 *
589
	 * These are the parameters set in the route registration.
590
	 *
591
	 * @since 4.4.0
592
	 * @access public
593
	 *
594
	 * @param array $params Parameter map of key to value.
595
	 */
596
	public function set_default_params( $params ) {
597
		$this->params['defaults'] = $params;
598
	}
599
600
	/**
601
	 * Retrieves the request body content.
602
	 *
603
	 * @since 4.4.0
604
	 * @access public
605
	 *
606
	 * @return string Binary data from the request body.
607
	 */
608
	public function get_body() {
609
		return $this->body;
610
	}
611
612
	/**
613
	 * Sets body content.
614
	 *
615
	 * @since 4.4.0
616
	 * @access public
617
	 *
618
	 * @param string $data Binary data from the request body.
619
	 */
620
	public function set_body( $data ) {
621
		$this->body = $data;
622
623
		// Enable lazy parsing.
624
		$this->parsed_json = false;
625
		$this->parsed_body = false;
626
		$this->params['JSON'] = null;
627
	}
628
629
	/**
630
	 * Retrieves the parameters from a JSON-formatted body.
631
	 *
632
	 * @since 4.4.0
633
	 * @access public
634
	 *
635
	 * @return array Parameter map of key to value.
636
	 */
637
	public function get_json_params() {
638
		// Ensure the parameters have been parsed out.
639
		$this->parse_json_params();
640
641
		return $this->params['JSON'];
642
	}
643
644
	/**
645
	 * Parses the JSON parameters.
646
	 *
647
	 * Avoids parsing the JSON data until we need to access it.
648
	 *
649
	 * @since 4.4.0
650
	 * @access protected
651
	 */
652
	protected function parse_json_params() {
653
		if ( $this->parsed_json ) {
654
			return;
655
		}
656
657
		$this->parsed_json = true;
658
659
		// Check that we actually got JSON.
660
		$content_type = $this->get_content_type();
661
662
		if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
663
			return;
664
		}
665
666
		$params = json_decode( $this->get_body(), true );
667
668
		/*
669
		 * Check for a parsing error.
670
		 *
671
		 * Note that due to WP's JSON compatibility functions, json_last_error
672
		 * might not be defined: https://core.trac.wordpress.org/ticket/27799
673
		 */
674
		if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
675
			return;
676
		}
677
678
		$this->params['JSON'] = $params;
679
	}
680
681
	/**
682
	 * Parses the request body parameters.
683
	 *
684
	 * Parses out URL-encoded bodies for request methods that aren't supported
685
	 * natively by PHP. In PHP 5.x, only POST has these parsed automatically.
686
	 *
687
	 * @since 4.4.0
688
	 * @access protected
689
	 */
690
	protected function parse_body_params() {
691
		if ( $this->parsed_body ) {
692
			return;
693
		}
694
695
		$this->parsed_body = true;
696
697
		/*
698
		 * Check that we got URL-encoded. Treat a missing content-type as
699
		 * URL-encoded for maximum compatibility.
700
		 */
701
		$content_type = $this->get_content_type();
702
703
		if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
704
			return;
705
		}
706
707
		parse_str( $this->get_body(), $params );
708
709
		/*
710
		 * Amazingly, parse_str follows magic quote rules. Sigh.
711
		 *
712
		 * NOTE: Do not refactor to use `wp_unslash`.
713
		 */
714
		if ( get_magic_quotes_gpc() ) {
715
			$params = stripslashes_deep( $params );
716
		}
717
718
		/*
719
		 * Add to the POST parameters stored internally. If a user has already
720
		 * set these manually (via `set_body_params`), don't override them.
721
		 */
722
		$this->params['POST'] = array_merge( $params, $this->params['POST'] );
723
	}
724
725
	/**
726
	 * Retrieves the route that matched the request.
727
	 *
728
	 * @since 4.4.0
729
	 * @access public
730
	 *
731
	 * @return string Route matching regex.
732
	 */
733
	public function get_route() {
734
		return $this->route;
735
	}
736
737
	/**
738
	 * Sets the route that matched the request.
739
	 *
740
	 * @since 4.4.0
741
	 * @access public
742
	 *
743
	 * @param string $route Route matching regex.
744
	 */
745
	public function set_route( $route ) {
746
		$this->route = $route;
747
	}
748
749
	/**
750
	 * Retrieves the attributes for the request.
751
	 *
752
	 * These are the options for the route that was matched.
753
	 *
754
	 * @since 4.4.0
755
	 * @access public
756
	 *
757
	 * @return array Attributes for the request.
758
	 */
759
	public function get_attributes() {
760
		return $this->attributes;
761
	}
762
763
	/**
764
	 * Sets the attributes for the request.
765
	 *
766
	 * @since 4.4.0
767
	 * @access public
768
	 *
769
	 * @param array $attributes Attributes for the request.
770
	 */
771
	public function set_attributes( $attributes ) {
772
		$this->attributes = $attributes;
773
	}
774
775
	/**
776
	 * Sanitizes (where possible) the params on the request.
777
	 *
778
	 * This is primarily based off the sanitize_callback param on each registered
779
	 * argument.
780
	 *
781
	 * @since 4.4.0
782
	 * @access public
783
	 *
784
	 * @return true|null True if there are no parameters to sanitize, null otherwise.
785
	 */
786
	public function sanitize_params() {
787
788
		$attributes = $this->get_attributes();
789
790
		// No arguments set, skip sanitizing.
791
		if ( empty( $attributes['args'] ) ) {
792
			return true;
793
		}
794
795
		$order = $this->get_parameter_order();
796
797
		foreach ( $order as $type ) {
798
			if ( empty( $this->params[ $type ] ) ) {
799
				continue;
800
			}
801
			foreach ( $this->params[ $type ] as $key => $value ) {
802
				// Check if this param has a sanitize_callback added.
803
				if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
804
					$this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
805
				}
806
			}
807
		}
808
		return null;
809
	}
810
811
	/**
812
	 * Checks whether this request is valid according to its attributes.
813
	 *
814
	 * @since 4.4.0
815
	 * @access public
816
	 *
817
	 * @return bool|WP_Error True if there are no parameters to validate or if all pass validation,
818
	 *                       WP_Error if required parameters are missing.
819
	 */
820
	public function has_valid_params() {
821
822
		$attributes = $this->get_attributes();
823
		$required = array();
824
825
		// No arguments set, skip validation.
826
		if ( empty( $attributes['args'] ) ) {
827
			return true;
828
		}
829
830
		foreach ( $attributes['args'] as $key => $arg ) {
831
832
			$param = $this->get_param( $key );
833
			if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
834
				$required[] = $key;
835
			}
836
		}
837
838 View Code Duplication
		if ( ! empty( $required ) ) {
839
			return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
840
		}
841
842
		/*
843
		 * Check the validation callbacks for each registered arg.
844
		 *
845
		 * This is done after required checking as required checking is cheaper.
846
		 */
847
		$invalid_params = array();
848
849
		foreach ( $attributes['args'] as $key => $arg ) {
850
851
			$param = $this->get_param( $key );
852
853
			if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
854
				$valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
855
856
				if ( false === $valid_check ) {
857
					$invalid_params[ $key ] = __( 'Invalid parameter.' );
858
				}
859
860
				if ( is_wp_error( $valid_check ) ) {
861
					$invalid_params[ $key ] = $valid_check->get_error_message();
862
				}
863
			}
864
		}
865
866 View Code Duplication
		if ( $invalid_params ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $invalid_params 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...
867
			return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), array( 'status' => 400, 'params' => $invalid_params ) );
868
		}
869
870
		return true;
871
872
	}
873
874
	/**
875
	 * Checks if a parameter is set.
876
	 *
877
	 * @since 4.4.0
878
	 * @access public
879
	 *
880
	 * @param string $offset Parameter name.
881
	 * @return bool Whether the parameter is set.
882
	 */
883
	public function offsetExists( $offset ) {
884
		$order = $this->get_parameter_order();
885
886
		foreach ( $order as $type ) {
887
			if ( isset( $this->params[ $type ][ $offset ] ) ) {
888
				return true;
889
			}
890
		}
891
892
		return false;
893
	}
894
895
	/**
896
	 * Retrieves a parameter from the request.
897
	 *
898
	 * @since 4.4.0
899
	 * @access public
900
	 *
901
	 * @param string $offset Parameter name.
902
	 * @return mixed|null Value if set, null otherwise.
903
	 */
904
	public function offsetGet( $offset ) {
905
		return $this->get_param( $offset );
906
	}
907
908
	/**
909
	 * Sets a parameter on the request.
910
	 *
911
	 * @since 4.4.0
912
	 * @access public
913
	 *
914
	 * @param string $offset Parameter name.
915
	 * @param mixed  $value  Parameter value.
916
	 */
917
	public function offsetSet( $offset, $value ) {
918
		$this->set_param( $offset, $value );
919
	}
920
921
	/**
922
	 * Removes a parameter from the request.
923
	 *
924
	 * @since 4.4.0
925
	 * @access public
926
	 *
927
	 * @param string $offset Parameter name.
928
	 */
929
	public function offsetUnset( $offset ) {
930
		$order = $this->get_parameter_order();
931
932
		// Remove the offset from every group.
933
		foreach ( $order as $type ) {
934
			unset( $this->params[ $type ][ $offset ] );
935
		}
936
	}
937
938
	/**
939
	 * Retrieves a WP_REST_Request object from a full URL.
940
	 *
941
	 * @static
942
	 * @since 4.5.0
943
	 * @access public
944
	 *
945
	 * @param string $url URL with protocol, domain, path and query args.
946
	 * @return WP_REST_Request|false WP_REST_Request object on success, false on failure.
947
	 */
948
	public static function from_url( $url ) {
949
		$bits = parse_url( $url );
950
		$query_params = array();
951
952
		if ( ! empty( $bits['query'] ) ) {
953
			wp_parse_str( $bits['query'], $query_params );
954
		}
955
956
		$api_root = rest_url();
957
		if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) {
958
			// Pretty permalinks on, and URL is under the API root
959
			$api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) );
960
			$route = parse_url( $api_url_part, PHP_URL_PATH );
961
		} elseif ( ! empty( $query_params['rest_route'] ) ) {
962
			// ?rest_route=... set directly
963
			$route = $query_params['rest_route'];
964
			unset( $query_params['rest_route'] );
965
		}
966
967
		$request = false;
968
		if ( ! empty( $route ) ) {
969
			$request = new WP_REST_Request( 'GET', $route );
970
			$request->set_query_params( $query_params );
971
		}
972
973
		/**
974
		 * Filter the request generated from a URL.
975
		 *
976
		 * @since 4.5.0
977
		 *
978
		 * @param WP_REST_Request|false $request Generated request object, or false if URL
979
		 *                                       could not be parsed.
980
		 * @param string                $url     URL the request was generated from.
981
		 */
982
		return apply_filters( 'rest_request_from_url', $request, $url );
983
	}
984
}
985