[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/ -> class-wp-rest-request.php (source)

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


Generated : Wed Nov 4 08:20:38 2026 Cross-referenced by PHPXref