您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

1036 行
24 KiB

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