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


Generated : Wed Jul 8 08:20:14 2026 Cross-referenced by PHPXref