home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api / class-wp-rest-request.php next >
Encoding:
PHP Script  |  2017-07-30  |  23.3 KB  |  984 lines

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