/wp-includes/rest-api/class-wp-rest-request.php

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