[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * REST API functions.
   4   *
   5   * @package WordPress
   6   * @subpackage REST_API
   7   * @since 4.4.0
   8   */
   9  
  10  /**
  11   * Version number for our API.
  12   *
  13   * @var string
  14   */
  15  define( 'REST_API_VERSION', '2.0' );
  16  
  17  /**
  18   * Registers a REST API route.
  19   *
  20   * Note: Do not use before the {@see 'rest_api_init'} hook.
  21   *
  22   * @since 4.4.0
  23   * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook.
  24   * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set.
  25   *
  26   * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
  27   * @param string $route     The base URL for route you are adding.
  28   * @param array  $args      Optional. Either an array of options for the endpoint, or an array of arrays for
  29   *                          multiple methods. Default empty array.
  30   * @param bool   $override  Optional. If the route already exists, should we override it? True overrides,
  31   *                          false merges (with newer overriding if duplicate keys exist). Default false.
  32   * @return bool True on success, false on error.
  33   */
  34  function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
  35      if ( empty( $namespace ) ) {
  36          /*
  37           * Non-namespaced routes are not allowed, with the exception of the main
  38           * and namespace indexes. If you really need to register a
  39           * non-namespaced route, call `WP_REST_Server::register_route` directly.
  40           */
  41          _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
  42          return false;
  43      } elseif ( empty( $route ) ) {
  44          _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
  45          return false;
  46      }
  47  
  48      $clean_namespace = trim( $namespace, '/' );
  49  
  50      if ( $clean_namespace !== $namespace ) {
  51          _doing_it_wrong( __FUNCTION__, __( 'Namespace must not start or end with a slash.' ), '5.4.2' );
  52      }
  53  
  54      if ( ! did_action( 'rest_api_init' ) ) {
  55          _doing_it_wrong(
  56              'register_rest_route',
  57              sprintf(
  58                  /* translators: %s: rest_api_init */
  59                  __( 'REST API routes must be registered on the %s action.' ),
  60                  '<code>rest_api_init</code>'
  61              ),
  62              '5.1.0'
  63          );
  64      }
  65  
  66      if ( isset( $args['args'] ) ) {
  67          $common_args = $args['args'];
  68          unset( $args['args'] );
  69      } else {
  70          $common_args = array();
  71      }
  72  
  73      if ( isset( $args['callback'] ) ) {
  74          // Upgrade a single set to multiple.
  75          $args = array( $args );
  76      }
  77  
  78      $defaults = array(
  79          'methods'  => 'GET',
  80          'callback' => null,
  81          'args'     => array(),
  82      );
  83  
  84      foreach ( $args as $key => &$arg_group ) {
  85          if ( ! is_numeric( $key ) ) {
  86              // Route option, skip here.
  87              continue;
  88          }
  89  
  90          $arg_group         = array_merge( $defaults, $arg_group );
  91          $arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
  92  
  93          if ( ! isset( $arg_group['permission_callback'] ) ) {
  94              _doing_it_wrong(
  95                  __FUNCTION__,
  96                  sprintf(
  97                      /* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */
  98                      __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ),
  99                      '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
 100                      '<code>permission_callback</code>',
 101                      '<code>__return_true</code>'
 102                  ),
 103                  '5.5.0'
 104              );
 105          }
 106      }
 107  
 108      $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' );
 109      rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override );
 110      return true;
 111  }
 112  
 113  /**
 114   * Registers a new field on an existing WordPress object type.
 115   *
 116   * @since 4.7.0
 117   *
 118   * @global array $wp_rest_additional_fields Holds registered fields, organized
 119   *                                          by object type.
 120   *
 121   * @param string|array $object_type Object(s) the field is being registered to,
 122   *                                  "post"|"term"|"comment" etc.
 123   * @param string       $attribute   The attribute name.
 124   * @param array        $args {
 125   *     Optional. An array of arguments used to handle the registered field.
 126   *
 127   *     @type callable|null $get_callback    Optional. The callback function used to retrieve the field value. Default is
 128   *                                          'null', the field will not be returned in the response. The function will
 129   *                                          be passed the prepared object data.
 130   *     @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
 131   *                                          is 'null', the value cannot be set or updated. The function will be passed
 132   *                                          the model object, like WP_Post.
 133   *     @type array|null $schema             Optional. The schema for this field.
 134   *                                          Default is 'null', no schema entry will be returned.
 135   * }
 136   */
 137  function register_rest_field( $object_type, $attribute, $args = array() ) {
 138      global $wp_rest_additional_fields;
 139  
 140      $defaults = array(
 141          'get_callback'    => null,
 142          'update_callback' => null,
 143          'schema'          => null,
 144      );
 145  
 146      $args = wp_parse_args( $args, $defaults );
 147  
 148      $object_types = (array) $object_type;
 149  
 150      foreach ( $object_types as $object_type ) {
 151          $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
 152      }
 153  }
 154  
 155  /**
 156   * Registers rewrite rules for the REST API.
 157   *
 158   * @since 4.4.0
 159   *
 160   * @see rest_api_register_rewrites()
 161   * @global WP $wp Current WordPress environment instance.
 162   */
 163  function rest_api_init() {
 164      rest_api_register_rewrites();
 165  
 166      global $wp;
 167      $wp->add_query_var( 'rest_route' );
 168  }
 169  
 170  /**
 171   * Adds REST rewrite rules.
 172   *
 173   * @since 4.4.0
 174   *
 175   * @see add_rewrite_rule()
 176   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 177   */
 178  function rest_api_register_rewrites() {
 179      global $wp_rewrite;
 180  
 181      add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
 182      add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
 183      add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
 184      add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
 185  }
 186  
 187  /**
 188   * Registers the default REST API filters.
 189   *
 190   * Attached to the {@see 'rest_api_init'} action
 191   * to make testing and disabling these filters easier.
 192   *
 193   * @since 4.4.0
 194   */
 195  function rest_api_default_filters() {
 196      if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
 197          // Deprecated reporting.
 198          add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
 199          add_filter( 'deprecated_function_trigger_error', '__return_false' );
 200          add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
 201          add_filter( 'deprecated_argument_trigger_error', '__return_false' );
 202          add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 );
 203          add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
 204      }
 205  
 206      // Default serving.
 207      add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
 208      add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
 209      add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
 210  
 211      add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
 212      add_filter( 'rest_index', 'rest_add_application_passwords_to_index' );
 213  }
 214  
 215  /**
 216   * Registers default REST API routes.
 217   *
 218   * @since 4.7.0
 219   */
 220  function create_initial_rest_routes() {
 221      foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
 222          $controller = $post_type->get_rest_controller();
 223  
 224          if ( ! $controller ) {
 225              continue;
 226          }
 227  
 228          $controller->register_routes();
 229  
 230          if ( post_type_supports( $post_type->name, 'revisions' ) ) {
 231              $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
 232              $revisions_controller->register_routes();
 233          }
 234  
 235          if ( 'attachment' !== $post_type->name ) {
 236              $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
 237              $autosaves_controller->register_routes();
 238          }
 239      }
 240  
 241      // Post types.
 242      $controller = new WP_REST_Post_Types_Controller;
 243      $controller->register_routes();
 244  
 245      // Post statuses.
 246      $controller = new WP_REST_Post_Statuses_Controller;
 247      $controller->register_routes();
 248  
 249      // Taxonomies.
 250      $controller = new WP_REST_Taxonomies_Controller;
 251      $controller->register_routes();
 252  
 253      // Terms.
 254      foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
 255          $controller = $taxonomy->get_rest_controller();
 256  
 257          if ( ! $controller ) {
 258              continue;
 259          }
 260  
 261          $controller->register_routes();
 262      }
 263  
 264      // Users.
 265      $controller = new WP_REST_Users_Controller;
 266      $controller->register_routes();
 267  
 268      // Application Passwords
 269      $controller = new WP_REST_Application_Passwords_Controller();
 270      $controller->register_routes();
 271  
 272      // Comments.
 273      $controller = new WP_REST_Comments_Controller;
 274      $controller->register_routes();
 275  
 276      $search_handlers = array(
 277          new WP_REST_Post_Search_Handler(),
 278          new WP_REST_Term_Search_Handler(),
 279          new WP_REST_Post_Format_Search_Handler(),
 280      );
 281  
 282      /**
 283       * Filters the search handlers to use in the REST search controller.
 284       *
 285       * @since 5.0.0
 286       *
 287       * @param array $search_handlers List of search handlers to use in the controller. Each search
 288       *                               handler instance must extend the `WP_REST_Search_Handler` class.
 289       *                               Default is only a handler for posts.
 290       */
 291      $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers );
 292  
 293      $controller = new WP_REST_Search_Controller( $search_handlers );
 294      $controller->register_routes();
 295  
 296      // Block Renderer.
 297      $controller = new WP_REST_Block_Renderer_Controller;
 298      $controller->register_routes();
 299  
 300      // Block Types.
 301      $controller = new WP_REST_Block_Types_Controller();
 302      $controller->register_routes();
 303  
 304      // Settings.
 305      $controller = new WP_REST_Settings_Controller;
 306      $controller->register_routes();
 307  
 308      // Themes.
 309      $controller = new WP_REST_Themes_Controller;
 310      $controller->register_routes();
 311  
 312      // Plugins.
 313      $controller = new WP_REST_Plugins_Controller();
 314      $controller->register_routes();
 315  
 316      // Sidebars.
 317      $controller = new WP_REST_Sidebars_Controller();
 318      $controller->register_routes();
 319  
 320      // Widget Types.
 321      $controller = new WP_REST_Widget_Types_Controller();
 322      $controller->register_routes();
 323  
 324      // Widgets.
 325      $controller = new WP_REST_Widgets_Controller();
 326      $controller->register_routes();
 327  
 328      // Block Directory.
 329      $controller = new WP_REST_Block_Directory_Controller();
 330      $controller->register_routes();
 331  
 332      // Pattern Directory.
 333      $controller = new WP_REST_Pattern_Directory_Controller();
 334      $controller->register_routes();
 335  
 336      // Site Health.
 337      $site_health = WP_Site_Health::get_instance();
 338      $controller  = new WP_REST_Site_Health_Controller( $site_health );
 339      $controller->register_routes();
 340  }
 341  
 342  /**
 343   * Loads the REST API.
 344   *
 345   * @since 4.4.0
 346   *
 347   * @global WP $wp Current WordPress environment instance.
 348   */
 349  function rest_api_loaded() {
 350      if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
 351          return;
 352      }
 353  
 354      /**
 355       * Whether this is a REST Request.
 356       *
 357       * @since 4.4.0
 358       * @var bool
 359       */
 360      define( 'REST_REQUEST', true );
 361  
 362      // Initialize the server.
 363      $server = rest_get_server();
 364  
 365      // Fire off the request.
 366      $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
 367      if ( empty( $route ) ) {
 368          $route = '/';
 369      }
 370      $server->serve_request( $route );
 371  
 372      // We're done.
 373      die();
 374  }
 375  
 376  /**
 377   * Retrieves the URL prefix for any API resource.
 378   *
 379   * @since 4.4.0
 380   *
 381   * @return string Prefix.
 382   */
 383  function rest_get_url_prefix() {
 384      /**
 385       * Filters the REST URL prefix.
 386       *
 387       * @since 4.4.0
 388       *
 389       * @param string $prefix URL prefix. Default 'wp-json'.
 390       */
 391      return apply_filters( 'rest_url_prefix', 'wp-json' );
 392  }
 393  
 394  /**
 395   * Retrieves the URL to a REST endpoint on a site.
 396   *
 397   * Note: The returned URL is NOT escaped.
 398   *
 399   * @since 4.4.0
 400   *
 401   * @todo Check if this is even necessary
 402   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 403   *
 404   * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog.
 405   * @param string   $path    Optional. REST route. Default '/'.
 406   * @param string   $scheme  Optional. Sanitization scheme. Default 'rest'.
 407   * @return string Full URL to the endpoint.
 408   */
 409  function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
 410      if ( empty( $path ) ) {
 411          $path = '/';
 412      }
 413  
 414      $path = '/' . ltrim( $path, '/' );
 415  
 416      if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
 417          global $wp_rewrite;
 418  
 419          if ( $wp_rewrite->using_index_permalinks() ) {
 420              $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
 421          } else {
 422              $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
 423          }
 424  
 425          $url .= $path;
 426      } else {
 427          $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
 428          // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php.
 429          // To work around this, we manually add index.php to the URL, avoiding the redirect.
 430          if ( 'index.php' !== substr( $url, 9 ) ) {
 431              $url .= 'index.php';
 432          }
 433  
 434          $url = add_query_arg( 'rest_route', $path, $url );
 435      }
 436  
 437      if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) {
 438          // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
 439          if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) {
 440              $url = set_url_scheme( $url, 'https' );
 441          }
 442      }
 443  
 444      if ( is_admin() && force_ssl_admin() ) {
 445          /*
 446           * In this situation the home URL may be http:, and `is_ssl()` may be false,
 447           * but the admin is served over https: (one way or another), so REST API usage
 448           * will be blocked by browsers unless it is also served over HTTPS.
 449           */
 450          $url = set_url_scheme( $url, 'https' );
 451      }
 452  
 453      /**
 454       * Filters the REST URL.
 455       *
 456       * Use this filter to adjust the url returned by the get_rest_url() function.
 457       *
 458       * @since 4.4.0
 459       *
 460       * @param string   $url     REST URL.
 461       * @param string   $path    REST route.
 462       * @param int|null $blog_id Blog ID.
 463       * @param string   $scheme  Sanitization scheme.
 464       */
 465      return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
 466  }
 467  
 468  /**
 469   * Retrieves the URL to a REST endpoint.
 470   *
 471   * Note: The returned URL is NOT escaped.
 472   *
 473   * @since 4.4.0
 474   *
 475   * @param string $path   Optional. REST route. Default empty.
 476   * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
 477   * @return string Full URL to the endpoint.
 478   */
 479  function rest_url( $path = '', $scheme = 'rest' ) {
 480      return get_rest_url( null, $path, $scheme );
 481  }
 482  
 483  /**
 484   * Do a REST request.
 485   *
 486   * Used primarily to route internal requests through WP_REST_Server.
 487   *
 488   * @since 4.4.0
 489   *
 490   * @param WP_REST_Request|string $request Request.
 491   * @return WP_REST_Response REST response.
 492   */
 493  function rest_do_request( $request ) {
 494      $request = rest_ensure_request( $request );
 495      return rest_get_server()->dispatch( $request );
 496  }
 497  
 498  /**
 499   * Retrieves the current REST server instance.
 500   *
 501   * Instantiates a new instance if none exists already.
 502   *
 503   * @since 4.5.0
 504   *
 505   * @global WP_REST_Server $wp_rest_server REST server instance.
 506   *
 507   * @return WP_REST_Server REST server instance.
 508   */
 509  function rest_get_server() {
 510      /* @var WP_REST_Server $wp_rest_server */
 511      global $wp_rest_server;
 512  
 513      if ( empty( $wp_rest_server ) ) {
 514          /**
 515           * Filters the REST Server Class.
 516           *
 517           * This filter allows you to adjust the server class used by the REST API, using a
 518           * different class to handle requests.
 519           *
 520           * @since 4.4.0
 521           *
 522           * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
 523           */
 524          $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
 525          $wp_rest_server       = new $wp_rest_server_class;
 526  
 527          /**
 528           * Fires when preparing to serve a REST API request.
 529           *
 530           * Endpoint objects should be created and register their hooks on this action rather
 531           * than another action to ensure they're only loaded when needed.
 532           *
 533           * @since 4.4.0
 534           *
 535           * @param WP_REST_Server $wp_rest_server Server object.
 536           */
 537          do_action( 'rest_api_init', $wp_rest_server );
 538      }
 539  
 540      return $wp_rest_server;
 541  }
 542  
 543  /**
 544   * Ensures request arguments are a request object (for consistency).
 545   *
 546   * @since 4.4.0
 547   * @since 5.3.0 Accept string argument for the request path.
 548   *
 549   * @param array|string|WP_REST_Request $request Request to check.
 550   * @return WP_REST_Request REST request instance.
 551   */
 552  function rest_ensure_request( $request ) {
 553      if ( $request instanceof WP_REST_Request ) {
 554          return $request;
 555      }
 556  
 557      if ( is_string( $request ) ) {
 558          return new WP_REST_Request( 'GET', $request );
 559      }
 560  
 561      return new WP_REST_Request( 'GET', '', $request );
 562  }
 563  
 564  /**
 565   * Ensures a REST response is a response object (for consistency).
 566   *
 567   * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc
 568   * without needing to double-check the object. Will also allow WP_Error to indicate error
 569   * responses, so users should immediately check for this value.
 570   *
 571   * @since 4.4.0
 572   *
 573   * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check.
 574   * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response
 575   *                                   is already an instance, WP_REST_Response, otherwise
 576   *                                   returns a new WP_REST_Response instance.
 577   */
 578  function rest_ensure_response( $response ) {
 579      if ( is_wp_error( $response ) ) {
 580          return $response;
 581      }
 582  
 583      if ( $response instanceof WP_REST_Response ) {
 584          return $response;
 585      }
 586  
 587      // While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide
 588      // all the required methods used in WP_REST_Server::dispatch().
 589      if ( $response instanceof WP_HTTP_Response ) {
 590          return new WP_REST_Response(
 591              $response->get_data(),
 592              $response->get_status(),
 593              $response->get_headers()
 594          );
 595      }
 596  
 597      return new WP_REST_Response( $response );
 598  }
 599  
 600  /**
 601   * Handles _deprecated_function() errors.
 602   *
 603   * @since 4.4.0
 604   *
 605   * @param string $function    The function that was called.
 606   * @param string $replacement The function that should have been called.
 607   * @param string $version     Version.
 608   */
 609  function rest_handle_deprecated_function( $function, $replacement, $version ) {
 610      if ( ! WP_DEBUG || headers_sent() ) {
 611          return;
 612      }
 613      if ( ! empty( $replacement ) ) {
 614          /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */
 615          $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
 616      } else {
 617          /* translators: 1: Function name, 2: WordPress version number. */
 618          $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
 619      }
 620  
 621      header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
 622  }
 623  
 624  /**
 625   * Handles _deprecated_argument() errors.
 626   *
 627   * @since 4.4.0
 628   *
 629   * @param string $function    The function that was called.
 630   * @param string $message     A message regarding the change.
 631   * @param string $version     Version.
 632   */
 633  function rest_handle_deprecated_argument( $function, $message, $version ) {
 634      if ( ! WP_DEBUG || headers_sent() ) {
 635          return;
 636      }
 637      if ( $message ) {
 638          /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */
 639          $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
 640      } else {
 641          /* translators: 1: Function name, 2: WordPress version number. */
 642          $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
 643      }
 644  
 645      header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
 646  }
 647  
 648  /**
 649   * Handles _doing_it_wrong errors.
 650   *
 651   * @since 5.5.0
 652   *
 653   * @param string      $function The function that was called.
 654   * @param string      $message  A message explaining what has been done incorrectly.
 655   * @param string|null $version  The version of WordPress where the message was added.
 656   */
 657  function rest_handle_doing_it_wrong( $function, $message, $version ) {
 658      if ( ! WP_DEBUG || headers_sent() ) {
 659          return;
 660      }
 661  
 662      if ( $version ) {
 663          /* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */
 664          $string = __( '%1$s (since %2$s; %3$s)' );
 665          $string = sprintf( $string, $function, $version, $message );
 666      } else {
 667          /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */
 668          $string = __( '%1$s (%2$s)' );
 669          $string = sprintf( $string, $function, $message );
 670      }
 671  
 672      header( sprintf( 'X-WP-DoingItWrong: %s', $string ) );
 673  }
 674  
 675  /**
 676   * Sends Cross-Origin Resource Sharing headers with API requests.
 677   *
 678   * @since 4.4.0
 679   *
 680   * @param mixed $value Response data.
 681   * @return mixed Response data.
 682   */
 683  function rest_send_cors_headers( $value ) {
 684      $origin = get_http_origin();
 685  
 686      if ( $origin ) {
 687          // Requests from file:// and data: URLs send "Origin: null".
 688          if ( 'null' !== $origin ) {
 689              $origin = esc_url_raw( $origin );
 690          }
 691          header( 'Access-Control-Allow-Origin: ' . $origin );
 692          header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
 693          header( 'Access-Control-Allow-Credentials: true' );
 694          header( 'Vary: Origin', false );
 695      } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
 696          header( 'Vary: Origin', false );
 697      }
 698  
 699      return $value;
 700  }
 701  
 702  /**
 703   * Handles OPTIONS requests for the server.
 704   *
 705   * This is handled outside of the server code, as it doesn't obey normal route
 706   * mapping.
 707   *
 708   * @since 4.4.0
 709   *
 710   * @param mixed           $response Current response, either response or `null` to indicate pass-through.
 711   * @param WP_REST_Server  $handler  ResponseHandler instance (usually WP_REST_Server).
 712   * @param WP_REST_Request $request  The request that was used to make current response.
 713   * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
 714   */
 715  function rest_handle_options_request( $response, $handler, $request ) {
 716      if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
 717          return $response;
 718      }
 719  
 720      $response = new WP_REST_Response();
 721      $data     = array();
 722  
 723      foreach ( $handler->get_routes() as $route => $endpoints ) {
 724          $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches );
 725  
 726          if ( ! $match ) {
 727              continue;
 728          }
 729  
 730          $args = array();
 731          foreach ( $matches as $param => $value ) {
 732              if ( ! is_int( $param ) ) {
 733                  $args[ $param ] = $value;
 734              }
 735          }
 736  
 737          foreach ( $endpoints as $endpoint ) {
 738              // Remove the redundant preg_match() argument.
 739              unset( $args[0] );
 740  
 741              $request->set_url_params( $args );
 742              $request->set_attributes( $endpoint );
 743          }
 744  
 745          $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
 746          $response->set_matched_route( $route );
 747          break;
 748      }
 749  
 750      $response->set_data( $data );
 751      return $response;
 752  }
 753  
 754  /**
 755   * Sends the "Allow" header to state all methods that can be sent to the current route.
 756   *
 757   * @since 4.4.0
 758   *
 759   * @param WP_REST_Response $response Current response being served.
 760   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 761   * @param WP_REST_Request  $request  The request that was used to make current response.
 762   * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
 763   */
 764  function rest_send_allow_header( $response, $server, $request ) {
 765      $matched_route = $response->get_matched_route();
 766  
 767      if ( ! $matched_route ) {
 768          return $response;
 769      }
 770  
 771      $routes = $server->get_routes();
 772  
 773      $allowed_methods = array();
 774  
 775      // Get the allowed methods across the routes.
 776      foreach ( $routes[ $matched_route ] as $_handler ) {
 777          foreach ( $_handler['methods'] as $handler_method => $value ) {
 778  
 779              if ( ! empty( $_handler['permission_callback'] ) ) {
 780  
 781                  $permission = call_user_func( $_handler['permission_callback'], $request );
 782  
 783                  $allowed_methods[ $handler_method ] = true === $permission;
 784              } else {
 785                  $allowed_methods[ $handler_method ] = true;
 786              }
 787          }
 788      }
 789  
 790      // Strip out all the methods that are not allowed (false values).
 791      $allowed_methods = array_filter( $allowed_methods );
 792  
 793      if ( $allowed_methods ) {
 794          $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
 795      }
 796  
 797      return $response;
 798  }
 799  
 800  /**
 801   * Recursively computes the intersection of arrays using keys for comparison.
 802   *
 803   * @since 5.3.0
 804   *
 805   * @param array $array1 The array with master keys to check.
 806   * @param array $array2 An array to compare keys against.
 807   * @return array An associative array containing all the entries of array1 which have keys
 808   *               that are present in all arguments.
 809   */
 810  function _rest_array_intersect_key_recursive( $array1, $array2 ) {
 811      $array1 = array_intersect_key( $array1, $array2 );
 812      foreach ( $array1 as $key => $value ) {
 813          if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
 814              $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
 815          }
 816      }
 817      return $array1;
 818  }
 819  
 820  /**
 821   * Filters the REST API response to include only a white-listed set of response object fields.
 822   *
 823   * @since 4.8.0
 824   *
 825   * @param WP_REST_Response $response Current response being served.
 826   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 827   * @param WP_REST_Request  $request  The request that was used to make current response.
 828   * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
 829   */
 830  function rest_filter_response_fields( $response, $server, $request ) {
 831      if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
 832          return $response;
 833      }
 834  
 835      $data = $response->get_data();
 836  
 837      $fields = wp_parse_list( $request['_fields'] );
 838  
 839      if ( 0 === count( $fields ) ) {
 840          return $response;
 841      }
 842  
 843      // Trim off outside whitespace from the comma delimited list.
 844      $fields = array_map( 'trim', $fields );
 845  
 846      // Create nested array of accepted field hierarchy.
 847      $fields_as_keyed = array();
 848      foreach ( $fields as $field ) {
 849          $parts = explode( '.', $field );
 850          $ref   = &$fields_as_keyed;
 851          while ( count( $parts ) > 1 ) {
 852              $next = array_shift( $parts );
 853              if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) {
 854                  // Skip any sub-properties if their parent prop is already marked for inclusion.
 855                  break 2;
 856              }
 857              $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array();
 858              $ref          = &$ref[ $next ];
 859          }
 860          $last         = array_shift( $parts );
 861          $ref[ $last ] = true;
 862      }
 863  
 864      if ( wp_is_numeric_array( $data ) ) {
 865          $new_data = array();
 866          foreach ( $data as $item ) {
 867              $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
 868          }
 869      } else {
 870          $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
 871      }
 872  
 873      $response->set_data( $new_data );
 874  
 875      return $response;
 876  }
 877  
 878  /**
 879   * Given an array of fields to include in a response, some of which may be
 880   * `nested.fields`, determine whether the provided field should be included
 881   * in the response body.
 882   *
 883   * If a parent field is passed in, the presence of any nested field within
 884   * that parent will cause the method to return `true`. For example "title"
 885   * will return true if any of `title`, `title.raw` or `title.rendered` is
 886   * provided.
 887   *
 888   * @since 5.3.0
 889   *
 890   * @param string $field  A field to test for inclusion in the response body.
 891   * @param array  $fields An array of string fields supported by the endpoint.
 892   * @return bool Whether to include the field or not.
 893   */
 894  function rest_is_field_included( $field, $fields ) {
 895      if ( in_array( $field, $fields, true ) ) {
 896          return true;
 897      }
 898  
 899      foreach ( $fields as $accepted_field ) {
 900          // Check to see if $field is the parent of any item in $fields.
 901          // A field "parent" should be accepted if "parent.child" is accepted.
 902          if ( strpos( $accepted_field, "$field." ) === 0 ) {
 903              return true;
 904          }
 905          // Conversely, if "parent" is accepted, all "parent.child" fields
 906          // should also be accepted.
 907          if ( strpos( $field, "$accepted_field." ) === 0 ) {
 908              return true;
 909          }
 910      }
 911  
 912      return false;
 913  }
 914  
 915  /**
 916   * Adds the REST API URL to the WP RSD endpoint.
 917   *
 918   * @since 4.4.0
 919   *
 920   * @see get_rest_url()
 921   */
 922  function rest_output_rsd() {
 923      $api_root = get_rest_url();
 924  
 925      if ( empty( $api_root ) ) {
 926          return;
 927      }
 928      ?>
 929      <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
 930      <?php
 931  }
 932  
 933  /**
 934   * Outputs the REST API link tag into page header.
 935   *
 936   * @since 4.4.0
 937   *
 938   * @see get_rest_url()
 939   */
 940  function rest_output_link_wp_head() {
 941      $api_root = get_rest_url();
 942  
 943      if ( empty( $api_root ) ) {
 944          return;
 945      }
 946  
 947      printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
 948  
 949      $resource = rest_get_queried_resource_route();
 950  
 951      if ( $resource ) {
 952          printf( '<link rel="alternate" type="application/json" href="%s" />', esc_url( rest_url( $resource ) ) );
 953      }
 954  }
 955  
 956  /**
 957   * Sends a Link header for the REST API.
 958   *
 959   * @since 4.4.0
 960   */
 961  function rest_output_link_header() {
 962      if ( headers_sent() ) {
 963          return;
 964      }
 965  
 966      $api_root = get_rest_url();
 967  
 968      if ( empty( $api_root ) ) {
 969          return;
 970      }
 971  
 972      header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', esc_url_raw( $api_root ) ), false );
 973  
 974      $resource = rest_get_queried_resource_route();
 975  
 976      if ( $resource ) {
 977          header( sprintf( 'Link: <%s>; rel="alternate"; type="application/json"', esc_url_raw( rest_url( $resource ) ) ), false );
 978      }
 979  }
 980  
 981  /**
 982   * Checks for errors when using cookie-based authentication.
 983   *
 984   * WordPress' built-in cookie authentication is always active
 985   * for logged in users. However, the API has to check nonces
 986   * for each request to ensure users are not vulnerable to CSRF.
 987   *
 988   * @since 4.4.0
 989   *
 990   * @global mixed          $wp_rest_auth_cookie
 991   *
 992   * @param WP_Error|mixed $result Error from another authentication handler,
 993   *                               null if we should handle it, or another value if not.
 994   * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
 995   */
 996  function rest_cookie_check_errors( $result ) {
 997      if ( ! empty( $result ) ) {
 998          return $result;
 999      }
1000  
1001      global $wp_rest_auth_cookie;
1002  
1003      /*
1004       * Is cookie authentication being used? (If we get an auth
1005       * error, but we're still logged in, another authentication
1006       * must have been used).
1007       */
1008      if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
1009          return $result;
1010      }
1011  
1012      // Determine if there is a nonce.
1013      $nonce = null;
1014  
1015      if ( isset( $_REQUEST['_wpnonce'] ) ) {
1016          $nonce = $_REQUEST['_wpnonce'];
1017      } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
1018          $nonce = $_SERVER['HTTP_X_WP_NONCE'];
1019      }
1020  
1021      if ( null === $nonce ) {
1022          // No nonce at all, so act as if it's an unauthenticated request.
1023          wp_set_current_user( 0 );
1024          return true;
1025      }
1026  
1027      // Check the nonce.
1028      $result = wp_verify_nonce( $nonce, 'wp_rest' );
1029  
1030      if ( ! $result ) {
1031          return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
1032      }
1033  
1034      // Send a refreshed nonce in header.
1035      rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
1036  
1037      return true;
1038  }
1039  
1040  /**
1041   * Collects cookie authentication status.
1042   *
1043   * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
1044   *
1045   * @since 4.4.0
1046   *
1047   * @see current_action()
1048   * @global mixed $wp_rest_auth_cookie
1049   */
1050  function rest_cookie_collect_status() {
1051      global $wp_rest_auth_cookie;
1052  
1053      $status_type = current_action();
1054  
1055      if ( 'auth_cookie_valid' !== $status_type ) {
1056          $wp_rest_auth_cookie = substr( $status_type, 12 );
1057          return;
1058      }
1059  
1060      $wp_rest_auth_cookie = true;
1061  }
1062  
1063  /**
1064   * Collects the status of authenticating with an application password.
1065   *
1066   * @since 5.6.0
1067   * @since 5.7.0 Added the `$app_password` parameter.
1068   *
1069   * @global WP_User|WP_Error|null $wp_rest_application_password_status
1070   * @global string|null $wp_rest_application_password_uuid
1071   *
1072   * @param WP_Error $user_or_error The authenticated user or error instance.
1073   * @param array    $app_password  The Application Password used to authenticate.
1074   */
1075  function rest_application_password_collect_status( $user_or_error, $app_password = array() ) {
1076      global $wp_rest_application_password_status, $wp_rest_application_password_uuid;
1077  
1078      $wp_rest_application_password_status = $user_or_error;
1079  
1080      if ( empty( $app_password['uuid'] ) ) {
1081          $wp_rest_application_password_uuid = null;
1082      } else {
1083          $wp_rest_application_password_uuid = $app_password['uuid'];
1084      }
1085  }
1086  
1087  /**
1088   * Gets the Application Password used for authenticating the request.
1089   *
1090   * @since 5.7.0
1091   *
1092   * @global string|null $wp_rest_application_password_uuid
1093   *
1094   * @return string|null The App Password UUID, or null if Application Passwords was not used.
1095   */
1096  function rest_get_authenticated_app_password() {
1097      global $wp_rest_application_password_uuid;
1098  
1099      return $wp_rest_application_password_uuid;
1100  }
1101  
1102  /**
1103   * Checks for errors when using application password-based authentication.
1104   *
1105   * @since 5.6.0
1106   *
1107   * @global WP_User|WP_Error|null $wp_rest_application_password_status
1108   *
1109   * @param WP_Error|null|true $result Error from another authentication handler,
1110   *                                   null if we should handle it, or another value if not.
1111   * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true.
1112   */
1113  function rest_application_password_check_errors( $result ) {
1114      global $wp_rest_application_password_status;
1115  
1116      if ( ! empty( $result ) ) {
1117          return $result;
1118      }
1119  
1120      if ( is_wp_error( $wp_rest_application_password_status ) ) {
1121          $data = $wp_rest_application_password_status->get_error_data();
1122  
1123          if ( ! isset( $data['status'] ) ) {
1124              $data['status'] = 401;
1125          }
1126  
1127          $wp_rest_application_password_status->add_data( $data );
1128  
1129          return $wp_rest_application_password_status;
1130      }
1131  
1132      if ( $wp_rest_application_password_status instanceof WP_User ) {
1133          return true;
1134      }
1135  
1136      return $result;
1137  }
1138  
1139  /**
1140   * Adds Application Passwords info to the REST API index.
1141   *
1142   * @since 5.6.0
1143   *
1144   * @param WP_REST_Response $response The index response object.
1145   * @return WP_REST_Response
1146   */
1147  function rest_add_application_passwords_to_index( $response ) {
1148      if ( ! wp_is_application_passwords_available() ) {
1149          return $response;
1150      }
1151  
1152      $response->data['authentication']['application-passwords'] = array(
1153          'endpoints' => array(
1154              'authorization' => admin_url( 'authorize-application.php' ),
1155          ),
1156      );
1157  
1158      return $response;
1159  }
1160  
1161  /**
1162   * Retrieves the avatar urls in various sizes.
1163   *
1164   * @since 4.7.0
1165   *
1166   * @see get_avatar_url()
1167   *
1168   * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
1169   *                           user email, WP_User object, WP_Post object, or WP_Comment object.
1170   * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
1171   */
1172  function rest_get_avatar_urls( $id_or_email ) {
1173      $avatar_sizes = rest_get_avatar_sizes();
1174  
1175      $urls = array();
1176      foreach ( $avatar_sizes as $size ) {
1177          $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
1178      }
1179  
1180      return $urls;
1181  }
1182  
1183  /**
1184   * Retrieves the pixel sizes for avatars.
1185   *
1186   * @since 4.7.0
1187   *
1188   * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
1189   */
1190  function rest_get_avatar_sizes() {
1191      /**
1192       * Filters the REST avatar sizes.
1193       *
1194       * Use this filter to adjust the array of sizes returned by the
1195       * `rest_get_avatar_sizes` function.
1196       *
1197       * @since 4.4.0
1198       *
1199       * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
1200       *                     Default `[ 24, 48, 96 ]`.
1201       */
1202      return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
1203  }
1204  
1205  /**
1206   * Parses an RFC3339 time into a Unix timestamp.
1207   *
1208   * @since 4.4.0
1209   *
1210   * @param string $date      RFC3339 timestamp.
1211   * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
1212   *                          the timestamp's timezone. Default false.
1213   * @return int Unix timestamp.
1214   */
1215  function rest_parse_date( $date, $force_utc = false ) {
1216      if ( $force_utc ) {
1217          $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
1218      }
1219  
1220      $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
1221  
1222      if ( ! preg_match( $regex, $date, $matches ) ) {
1223          return false;
1224      }
1225  
1226      return strtotime( $date );
1227  }
1228  
1229  /**
1230   * Parses a 3 or 6 digit hex color (with #).
1231   *
1232   * @since 5.4.0
1233   *
1234   * @param string $color 3 or 6 digit hex color (with #).
1235   * @return string|false
1236   */
1237  function rest_parse_hex_color( $color ) {
1238      $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|';
1239      if ( ! preg_match( $regex, $color, $matches ) ) {
1240          return false;
1241      }
1242  
1243      return $color;
1244  }
1245  
1246  /**
1247   * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
1248   *
1249   * @since 4.4.0
1250   *
1251   * @see rest_parse_date()
1252   *
1253   * @param string $date   RFC3339 timestamp.
1254   * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
1255   * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
1256   *                    null on failure.
1257   */
1258  function rest_get_date_with_gmt( $date, $is_utc = false ) {
1259      /*
1260       * Whether or not the original date actually has a timezone string
1261       * changes the way we need to do timezone conversion.
1262       * Store this info before parsing the date, and use it later.
1263       */
1264      $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
1265  
1266      $date = rest_parse_date( $date );
1267  
1268      if ( empty( $date ) ) {
1269          return null;
1270      }
1271  
1272      /*
1273       * At this point $date could either be a local date (if we were passed
1274       * a *local* date without a timezone offset) or a UTC date (otherwise).
1275       * Timezone conversion needs to be handled differently between these two cases.
1276       */
1277      if ( ! $is_utc && ! $has_timezone ) {
1278          $local = gmdate( 'Y-m-d H:i:s', $date );
1279          $utc   = get_gmt_from_date( $local );
1280      } else {
1281          $utc   = gmdate( 'Y-m-d H:i:s', $date );
1282          $local = get_date_from_gmt( $utc );
1283      }
1284  
1285      return array( $local, $utc );
1286  }
1287  
1288  /**
1289   * Returns a contextual HTTP error code for authorization failure.
1290   *
1291   * @since 4.7.0
1292   *
1293   * @return int 401 if the user is not logged in, 403 if the user is logged in.
1294   */
1295  function rest_authorization_required_code() {
1296      return is_user_logged_in() ? 403 : 401;
1297  }
1298  
1299  /**
1300   * Validate a request argument based on details registered to the route.
1301   *
1302   * @since 4.7.0
1303   *
1304   * @param mixed           $value
1305   * @param WP_REST_Request $request
1306   * @param string          $param
1307   * @return true|WP_Error
1308   */
1309  function rest_validate_request_arg( $value, $request, $param ) {
1310      $attributes = $request->get_attributes();
1311      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1312          return true;
1313      }
1314      $args = $attributes['args'][ $param ];
1315  
1316      return rest_validate_value_from_schema( $value, $args, $param );
1317  }
1318  
1319  /**
1320   * Sanitize a request argument based on details registered to the route.
1321   *
1322   * @since 4.7.0
1323   *
1324   * @param mixed           $value
1325   * @param WP_REST_Request $request
1326   * @param string          $param
1327   * @return mixed
1328   */
1329  function rest_sanitize_request_arg( $value, $request, $param ) {
1330      $attributes = $request->get_attributes();
1331      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1332          return $value;
1333      }
1334      $args = $attributes['args'][ $param ];
1335  
1336      return rest_sanitize_value_from_schema( $value, $args, $param );
1337  }
1338  
1339  /**
1340   * Parse a request argument based on details registered to the route.
1341   *
1342   * Runs a validation check and sanitizes the value, primarily to be used via
1343   * the `sanitize_callback` arguments in the endpoint args registration.
1344   *
1345   * @since 4.7.0
1346   *
1347   * @param mixed           $value
1348   * @param WP_REST_Request $request
1349   * @param string          $param
1350   * @return mixed
1351   */
1352  function rest_parse_request_arg( $value, $request, $param ) {
1353      $is_valid = rest_validate_request_arg( $value, $request, $param );
1354  
1355      if ( is_wp_error( $is_valid ) ) {
1356          return $is_valid;
1357      }
1358  
1359      $value = rest_sanitize_request_arg( $value, $request, $param );
1360  
1361      return $value;
1362  }
1363  
1364  /**
1365   * Determines if an IP address is valid.
1366   *
1367   * Handles both IPv4 and IPv6 addresses.
1368   *
1369   * @since 4.7.0
1370   *
1371   * @param string $ip IP address.
1372   * @return string|false The valid IP address, otherwise false.
1373   */
1374  function rest_is_ip_address( $ip ) {
1375      $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
1376  
1377      if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
1378          return false;
1379      }
1380  
1381      return $ip;
1382  }
1383  
1384  /**
1385   * Changes a boolean-like value into the proper boolean value.
1386   *
1387   * @since 4.7.0
1388   *
1389   * @param bool|string|int $value The value being evaluated.
1390   * @return bool Returns the proper associated boolean value.
1391   */
1392  function rest_sanitize_boolean( $value ) {
1393      // String values are translated to `true`; make sure 'false' is false.
1394      if ( is_string( $value ) ) {
1395          $value = strtolower( $value );
1396          if ( in_array( $value, array( 'false', '0' ), true ) ) {
1397              $value = false;
1398          }
1399      }
1400  
1401      // Everything else will map nicely to boolean.
1402      return (bool) $value;
1403  }
1404  
1405  /**
1406   * Determines if a given value is boolean-like.
1407   *
1408   * @since 4.7.0
1409   *
1410   * @param bool|string $maybe_bool The value being evaluated.
1411   * @return bool True if a boolean, otherwise false.
1412   */
1413  function rest_is_boolean( $maybe_bool ) {
1414      if ( is_bool( $maybe_bool ) ) {
1415          return true;
1416      }
1417  
1418      if ( is_string( $maybe_bool ) ) {
1419          $maybe_bool = strtolower( $maybe_bool );
1420  
1421          $valid_boolean_values = array(
1422              'false',
1423              'true',
1424              '0',
1425              '1',
1426          );
1427  
1428          return in_array( $maybe_bool, $valid_boolean_values, true );
1429      }
1430  
1431      if ( is_int( $maybe_bool ) ) {
1432          return in_array( $maybe_bool, array( 0, 1 ), true );
1433      }
1434  
1435      return false;
1436  }
1437  
1438  /**
1439   * Determines if a given value is integer-like.
1440   *
1441   * @since 5.5.0
1442   *
1443   * @param mixed $maybe_integer The value being evaluated.
1444   * @return bool True if an integer, otherwise false.
1445   */
1446  function rest_is_integer( $maybe_integer ) {
1447      return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer;
1448  }
1449  
1450  /**
1451   * Determines if a given value is array-like.
1452   *
1453   * @since 5.5.0
1454   *
1455   * @param mixed $maybe_array The value being evaluated.
1456   * @return bool
1457   */
1458  function rest_is_array( $maybe_array ) {
1459      if ( is_scalar( $maybe_array ) ) {
1460          $maybe_array = wp_parse_list( $maybe_array );
1461      }
1462  
1463      return wp_is_numeric_array( $maybe_array );
1464  }
1465  
1466  /**
1467   * Converts an array-like value to an array.
1468   *
1469   * @since 5.5.0
1470   *
1471   * @param mixed $maybe_array The value being evaluated.
1472   * @return array Returns the array extracted from the value.
1473   */
1474  function rest_sanitize_array( $maybe_array ) {
1475      if ( is_scalar( $maybe_array ) ) {
1476          return wp_parse_list( $maybe_array );
1477      }
1478  
1479      if ( ! is_array( $maybe_array ) ) {
1480          return array();
1481      }
1482  
1483      // Normalize to numeric array so nothing unexpected is in the keys.
1484      return array_values( $maybe_array );
1485  }
1486  
1487  /**
1488   * Determines if a given value is object-like.
1489   *
1490   * @since 5.5.0
1491   *
1492   * @param mixed $maybe_object The value being evaluated.
1493   * @return bool True if object like, otherwise false.
1494   */
1495  function rest_is_object( $maybe_object ) {
1496      if ( '' === $maybe_object ) {
1497          return true;
1498      }
1499  
1500      if ( $maybe_object instanceof stdClass ) {
1501          return true;
1502      }
1503  
1504      if ( $maybe_object instanceof JsonSerializable ) {
1505          $maybe_object = $maybe_object->jsonSerialize();
1506      }
1507  
1508      return is_array( $maybe_object );
1509  }
1510  
1511  /**
1512   * Converts an object-like value to an object.
1513   *
1514   * @since 5.5.0
1515   *
1516   * @param mixed $maybe_object The value being evaluated.
1517   * @return array Returns the object extracted from the value.
1518   */
1519  function rest_sanitize_object( $maybe_object ) {
1520      if ( '' === $maybe_object ) {
1521          return array();
1522      }
1523  
1524      if ( $maybe_object instanceof stdClass ) {
1525          return (array) $maybe_object;
1526      }
1527  
1528      if ( $maybe_object instanceof JsonSerializable ) {
1529          $maybe_object = $maybe_object->jsonSerialize();
1530      }
1531  
1532      if ( ! is_array( $maybe_object ) ) {
1533          return array();
1534      }
1535  
1536      return $maybe_object;
1537  }
1538  
1539  /**
1540   * Gets the best type for a value.
1541   *
1542   * @since 5.5.0
1543   *
1544   * @param mixed $value The value to check.
1545   * @param array $types The list of possible types.
1546   * @return string The best matching type, an empty string if no types match.
1547   */
1548  function rest_get_best_type_for_value( $value, $types ) {
1549      static $checks = array(
1550          'array'   => 'rest_is_array',
1551          'object'  => 'rest_is_object',
1552          'integer' => 'rest_is_integer',
1553          'number'  => 'is_numeric',
1554          'boolean' => 'rest_is_boolean',
1555          'string'  => 'is_string',
1556          'null'    => 'is_null',
1557      );
1558  
1559      // Both arrays and objects allow empty strings to be converted to their types.
1560      // But the best answer for this type is a string.
1561      if ( '' === $value && in_array( 'string', $types, true ) ) {
1562          return 'string';
1563      }
1564  
1565      foreach ( $types as $type ) {
1566          if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
1567              return $type;
1568          }
1569      }
1570  
1571      return '';
1572  }
1573  
1574  /**
1575   * Handles getting the best type for a multi-type schema.
1576   *
1577   * This is a wrapper for {@see rest_get_best_type_for_value()} that handles
1578   * backward compatibility for schemas that use invalid types.
1579   *
1580   * @since 5.5.0
1581   *
1582   * @param mixed  $value The value to check.
1583   * @param array  $args  The schema array to use.
1584   * @param string $param The parameter name, used in error messages.
1585   * @return string
1586   */
1587  function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
1588      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
1589      $invalid_types = array_diff( $args['type'], $allowed_types );
1590  
1591      if ( $invalid_types ) {
1592          _doing_it_wrong(
1593              __FUNCTION__,
1594              /* translators: 1: Parameter, 2: List of allowed types. */
1595              wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
1596              '5.5.0'
1597          );
1598      }
1599  
1600      $best_type = rest_get_best_type_for_value( $value, $args['type'] );
1601  
1602      if ( ! $best_type ) {
1603          if ( ! $invalid_types ) {
1604              return '';
1605          }
1606  
1607          // Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
1608          $best_type = reset( $invalid_types );
1609      }
1610  
1611      return $best_type;
1612  }
1613  
1614  /**
1615   * Checks if an array is made up of unique items.
1616   *
1617   * @since 5.5.0
1618   *
1619   * @param array $array The array to check.
1620   * @return bool True if the array contains unique items, false otherwise.
1621   */
1622  function rest_validate_array_contains_unique_items( $array ) {
1623      $seen = array();
1624  
1625      foreach ( $array as $item ) {
1626          $stabilized = rest_stabilize_value( $item );
1627          $key        = serialize( $stabilized );
1628  
1629          if ( ! isset( $seen[ $key ] ) ) {
1630              $seen[ $key ] = true;
1631  
1632              continue;
1633          }
1634  
1635          return false;
1636      }
1637  
1638      return true;
1639  }
1640  
1641  /**
1642   * Stabilizes a value following JSON Schema semantics.
1643   *
1644   * For lists, order is preserved. For objects, properties are reordered alphabetically.
1645   *
1646   * @since 5.5.0
1647   *
1648   * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays.
1649   * @return mixed The stabilized value.
1650   */
1651  function rest_stabilize_value( $value ) {
1652      if ( is_scalar( $value ) || is_null( $value ) ) {
1653          return $value;
1654      }
1655  
1656      if ( is_object( $value ) ) {
1657          _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' );
1658  
1659          return $value;
1660      }
1661  
1662      ksort( $value );
1663  
1664      foreach ( $value as $k => $v ) {
1665          $value[ $k ] = rest_stabilize_value( $v );
1666      }
1667  
1668      return $value;
1669  }
1670  
1671  /**
1672   * Validates if the JSON Schema pattern matches a value.
1673   *
1674   * @since 5.6.0
1675   *
1676   * @param string $pattern The pattern to match against.
1677   * @param string $value   The value to check.
1678   * @return bool           True if the pattern matches the given value, false otherwise.
1679   */
1680  function rest_validate_json_schema_pattern( $pattern, $value ) {
1681      $escaped_pattern = str_replace( '#', '\\#', $pattern );
1682  
1683      return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
1684  }
1685  
1686  /**
1687   * Finds the schema for a property using the patternProperties keyword.
1688   *
1689   * @since 5.6.0
1690   *
1691   * @param string $property The property name to check.
1692   * @param array  $args     The schema array to use.
1693   * @return array|null      The schema of matching pattern property, or null if no patterns match.
1694   */
1695  function rest_find_matching_pattern_property_schema( $property, $args ) {
1696      if ( isset( $args['patternProperties'] ) ) {
1697          foreach ( $args['patternProperties'] as $pattern => $child_schema ) {
1698              if ( rest_validate_json_schema_pattern( $pattern, $property ) ) {
1699                  return $child_schema;
1700              }
1701          }
1702      }
1703  
1704      return null;
1705  }
1706  
1707  /**
1708   * Formats a combining operation error into a WP_Error object.
1709   *
1710   * @since 5.6.0
1711   *
1712   * @param string $param The parameter name.
1713   * @param array $error  The error details.
1714   * @return WP_Error
1715   */
1716  function rest_format_combining_operation_error( $param, $error ) {
1717      $position = $error['index'];
1718      $reason   = $error['error_object']->get_error_message();
1719  
1720      if ( isset( $error['schema']['title'] ) ) {
1721          $title = $error['schema']['title'];
1722  
1723          return new WP_Error(
1724              'rest_no_matching_schema',
1725              /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
1726              sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
1727              array( 'position' => $position )
1728          );
1729      }
1730  
1731      return new WP_Error(
1732          'rest_no_matching_schema',
1733          /* translators: 1: Parameter, 2: Reason. */
1734          sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
1735          array( 'position' => $position )
1736      );
1737  }
1738  
1739  /**
1740   * Gets the error of combining operation.
1741   *
1742   * @since 5.6.0
1743   *
1744   * @param array  $value  The value to validate.
1745   * @param string $param  The parameter name, used in error messages.
1746   * @param array  $errors The errors array, to search for possible error.
1747   * @return WP_Error      The combining operation error.
1748   */
1749  function rest_get_combining_operation_error( $value, $param, $errors ) {
1750      // If there is only one error, simply return it.
1751      if ( 1 === count( $errors ) ) {
1752          return rest_format_combining_operation_error( $param, $errors[0] );
1753      }
1754  
1755      // Filter out all errors related to type validation.
1756      $filtered_errors = array();
1757      foreach ( $errors as $error ) {
1758          $error_code = $error['error_object']->get_error_code();
1759          $error_data = $error['error_object']->get_error_data();
1760  
1761          if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
1762              $filtered_errors[] = $error;
1763          }
1764      }
1765  
1766      // If there is only one error left, simply return it.
1767      if ( 1 === count( $filtered_errors ) ) {
1768          return rest_format_combining_operation_error( $param, $filtered_errors[0] );
1769      }
1770  
1771      // If there are only errors related to object validation, try choosing the most appropriate one.
1772      if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
1773          $result = null;
1774          $number = 0;
1775  
1776          foreach ( $filtered_errors as $error ) {
1777              if ( isset( $error['schema']['properties'] ) ) {
1778                  $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
1779                  if ( $n > $number ) {
1780                      $result = $error;
1781                      $number = $n;
1782                  }
1783              }
1784          }
1785  
1786          if ( null !== $result ) {
1787              return rest_format_combining_operation_error( $param, $result );
1788          }
1789      }
1790  
1791      // If each schema has a title, include those titles in the error message.
1792      $schema_titles = array();
1793      foreach ( $errors as $error ) {
1794          if ( isset( $error['schema']['title'] ) ) {
1795              $schema_titles[] = $error['schema']['title'];
1796          }
1797      }
1798  
1799      if ( count( $schema_titles ) === count( $errors ) ) {
1800          /* translators: 1: Parameter, 2: Schema titles. */
1801          return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
1802      }
1803  
1804      /* translators: %s: Parameter. */
1805      return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) );
1806  }
1807  
1808  /**
1809   * Finds the matching schema among the "anyOf" schemas.
1810   *
1811   * @since 5.6.0
1812   *
1813   * @param mixed  $value   The value to validate.
1814   * @param array  $args    The schema array to use.
1815   * @param string $param   The parameter name, used in error messages.
1816   * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
1817   */
1818  function rest_find_any_matching_schema( $value, $args, $param ) {
1819      $errors = array();
1820  
1821      foreach ( $args['anyOf'] as $index => $schema ) {
1822          if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1823              $schema['type'] = $args['type'];
1824          }
1825  
1826          $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1827          if ( ! is_wp_error( $is_valid ) ) {
1828              return $schema;
1829          }
1830  
1831          $errors[] = array(
1832              'error_object' => $is_valid,
1833              'schema'       => $schema,
1834              'index'        => $index,
1835          );
1836      }
1837  
1838      return rest_get_combining_operation_error( $value, $param, $errors );
1839  }
1840  
1841  /**
1842   * Finds the matching schema among the "oneOf" schemas.
1843   *
1844   * @since 5.6.0
1845   *
1846   * @param mixed  $value                  The value to validate.
1847   * @param array  $args                   The schema array to use.
1848   * @param string $param                  The parameter name, used in error messages.
1849   * @param bool   $stop_after_first_match Optional. Whether the process should stop after the first successful match.
1850   * @return array|WP_Error                The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
1851   */
1852  function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
1853      $matching_schemas = array();
1854      $errors           = array();
1855  
1856      foreach ( $args['oneOf'] as $index => $schema ) {
1857          if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1858              $schema['type'] = $args['type'];
1859          }
1860  
1861          $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1862          if ( ! is_wp_error( $is_valid ) ) {
1863              if ( $stop_after_first_match ) {
1864                  return $schema;
1865              }
1866  
1867              $matching_schemas[] = array(
1868                  'schema_object' => $schema,
1869                  'index'         => $index,
1870              );
1871          } else {
1872              $errors[] = array(
1873                  'error_object' => $is_valid,
1874                  'schema'       => $schema,
1875                  'index'        => $index,
1876              );
1877          }
1878      }
1879  
1880      if ( ! $matching_schemas ) {
1881          return rest_get_combining_operation_error( $value, $param, $errors );
1882      }
1883  
1884      if ( count( $matching_schemas ) > 1 ) {
1885          $schema_positions = array();
1886          $schema_titles    = array();
1887  
1888          foreach ( $matching_schemas as $schema ) {
1889              $schema_positions[] = $schema['index'];
1890  
1891              if ( isset( $schema['schema_object']['title'] ) ) {
1892                  $schema_titles[] = $schema['schema_object']['title'];
1893              }
1894          }
1895  
1896          // If each schema has a title, include those titles in the error message.
1897          if ( count( $schema_titles ) === count( $matching_schemas ) ) {
1898              return new WP_Error(
1899                  'rest_one_of_multiple_matches',
1900                  /* translators: 1: Parameter, 2: Schema titles. */
1901                  wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
1902                  array( 'positions' => $schema_positions )
1903              );
1904          }
1905  
1906          return new WP_Error(
1907              'rest_one_of_multiple_matches',
1908              /* translators: %s: Parameter. */
1909              sprintf( __( '%s matches more than one of the expected formats.' ), $param ),
1910              array( 'positions' => $schema_positions )
1911          );
1912      }
1913  
1914      return $matching_schemas[0]['schema_object'];
1915  }
1916  
1917  /**
1918   * Checks the equality of two values, following JSON Schema semantics.
1919   *
1920   * Property order is ignored for objects.
1921   *
1922   * Values must have been previously sanitized/coerced to their native types.
1923   *
1924   * @since 5.7.0
1925   *
1926   * @param mixed $value1 The first value to check.
1927   * @param mixed $value2 The second value to check.
1928   * @return bool True if the values are equal or false otherwise.
1929   */
1930  function rest_are_values_equal( $value1, $value2 ) {
1931      if ( is_array( $value1 ) && is_array( $value2 ) ) {
1932          if ( count( $value1 ) !== count( $value2 ) ) {
1933              return false;
1934          }
1935  
1936          foreach ( $value1 as $index => $value ) {
1937              if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
1938                  return false;
1939              }
1940          }
1941  
1942          return true;
1943      }
1944  
1945      if ( is_int( $value1 ) && is_float( $value2 )
1946          || is_float( $value1 ) && is_int( $value2 )
1947      ) {
1948          return (float) $value1 === (float) $value2;
1949      }
1950  
1951      return $value1 === $value2;
1952  }
1953  
1954  /**
1955   * Validates that the given value is a member of the JSON Schema "enum".
1956   *
1957   * @since 5.7.0
1958   *
1959   * @param mixed  $value  The value to validate.
1960   * @param array  $args   The schema array to use.
1961   * @param string $param  The parameter name, used in error messages.
1962   * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
1963   */
1964  function rest_validate_enum( $value, $args, $param ) {
1965      $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
1966      if ( is_wp_error( $sanitized_value ) ) {
1967          return $sanitized_value;
1968      }
1969  
1970      foreach ( $args['enum'] as $enum_value ) {
1971          if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
1972              return true;
1973          }
1974      }
1975  
1976      $encoded_enum_values = array();
1977      foreach ( $args['enum'] as $enum_value ) {
1978          $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
1979      }
1980  
1981      if ( count( $encoded_enum_values ) === 1 ) {
1982          /* translators: 1: Parameter, 2: Valid values. */
1983          return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
1984      }
1985  
1986      /* translators: 1: Parameter, 2: List of valid values. */
1987      return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
1988  }
1989  
1990  /**
1991   * Get all valid JSON schema properties.
1992   *
1993   * @since 5.6.0
1994   *
1995   * @return string[] All valid JSON schema properties.
1996   */
1997  function rest_get_allowed_schema_keywords() {
1998      return array(
1999          'title',
2000          'description',
2001          'default',
2002          'type',
2003          'format',
2004          'enum',
2005          'items',
2006          'properties',
2007          'additionalProperties',
2008          'patternProperties',
2009          'minProperties',
2010          'maxProperties',
2011          'minimum',
2012          'maximum',
2013          'exclusiveMinimum',
2014          'exclusiveMaximum',
2015          'multipleOf',
2016          'minLength',
2017          'maxLength',
2018          'pattern',
2019          'minItems',
2020          'maxItems',
2021          'uniqueItems',
2022          'anyOf',
2023          'oneOf',
2024      );
2025  }
2026  
2027  /**
2028   * Validate a value based on a schema.
2029   *
2030   * @since 4.7.0
2031   * @since 4.9.0 Support the "object" type.
2032   * @since 5.2.0 Support validating "additionalProperties" against a schema.
2033   * @since 5.3.0 Support multiple types.
2034   * @since 5.4.0 Convert an empty string to an empty object.
2035   * @since 5.5.0 Add the "uuid" and "hex-color" formats.
2036   *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
2037   *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
2038   *              Validate required properties.
2039   * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
2040   *              Support the "multipleOf" keyword for numbers and integers.
2041   *              Support the "patternProperties" keyword for objects.
2042   *              Support the "anyOf" and "oneOf" keywords.
2043   *
2044   * @param mixed  $value The value to validate.
2045   * @param array  $args  Schema array to use for validation.
2046   * @param string $param The parameter name, used in error messages.
2047   * @return true|WP_Error
2048   */
2049  function rest_validate_value_from_schema( $value, $args, $param = '' ) {
2050      if ( isset( $args['anyOf'] ) ) {
2051          $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2052          if ( is_wp_error( $matching_schema ) ) {
2053              return $matching_schema;
2054          }
2055  
2056          if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2057              $args['type'] = $matching_schema['type'];
2058          }
2059      }
2060  
2061      if ( isset( $args['oneOf'] ) ) {
2062          $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2063          if ( is_wp_error( $matching_schema ) ) {
2064              return $matching_schema;
2065          }
2066  
2067          if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2068              $args['type'] = $matching_schema['type'];
2069          }
2070      }
2071  
2072      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2073  
2074      if ( ! isset( $args['type'] ) ) {
2075          /* translators: %s: Parameter. */
2076          _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2077      }
2078  
2079      if ( is_array( $args['type'] ) ) {
2080          $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2081  
2082          if ( ! $best_type ) {
2083              return new WP_Error(
2084                  'rest_invalid_type',
2085                  /* translators: 1: Parameter, 2: List of types. */
2086                  sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
2087                  array( 'param' => $param )
2088              );
2089          }
2090  
2091          $args['type'] = $best_type;
2092      }
2093  
2094      if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2095          _doing_it_wrong(
2096              __FUNCTION__,
2097              /* translators: 1: Parameter, 2: The list of allowed types. */
2098              wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2099              '5.5.0'
2100          );
2101      }
2102  
2103      switch ( $args['type'] ) {
2104          case 'null':
2105              $is_valid = rest_validate_null_value_from_schema( $value, $param );
2106              break;
2107          case 'boolean':
2108              $is_valid = rest_validate_boolean_value_from_schema( $value, $param );
2109              break;
2110          case 'object':
2111              $is_valid = rest_validate_object_value_from_schema( $value, $args, $param );
2112              break;
2113          case 'array':
2114              $is_valid = rest_validate_array_value_from_schema( $value, $args, $param );
2115              break;
2116          case 'number':
2117              $is_valid = rest_validate_number_value_from_schema( $value, $args, $param );
2118              break;
2119          case 'string':
2120              $is_valid = rest_validate_string_value_from_schema( $value, $args, $param );
2121              break;
2122          case 'integer':
2123              $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param );
2124              break;
2125          default:
2126              $is_valid = true;
2127              break;
2128      }
2129  
2130      if ( is_wp_error( $is_valid ) ) {
2131          return $is_valid;
2132      }
2133  
2134      if ( ! empty( $args['enum'] ) ) {
2135          $enum_contains_value = rest_validate_enum( $value, $args, $param );
2136          if ( is_wp_error( $enum_contains_value ) ) {
2137              return $enum_contains_value;
2138          }
2139      }
2140  
2141      // The "format" keyword should only be applied to strings. However, for backward compatibility,
2142      // we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
2143      if ( isset( $args['format'] )
2144          && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2145      ) {
2146          switch ( $args['format'] ) {
2147              case 'hex-color':
2148                  if ( ! rest_parse_hex_color( $value ) ) {
2149                      return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
2150                  }
2151                  break;
2152  
2153              case 'date-time':
2154                  if ( ! rest_parse_date( $value ) ) {
2155                      return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
2156                  }
2157                  break;
2158  
2159              case 'email':
2160                  if ( ! is_email( $value ) ) {
2161                      return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
2162                  }
2163                  break;
2164              case 'ip':
2165                  if ( ! rest_is_ip_address( $value ) ) {
2166                      /* translators: %s: IP address. */
2167                      return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) );
2168                  }
2169                  break;
2170              case 'uuid':
2171                  if ( ! wp_is_uuid( $value ) ) {
2172                      /* translators: %s: The name of a JSON field expecting a valid UUID. */
2173                      return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
2174                  }
2175                  break;
2176          }
2177      }
2178  
2179      return true;
2180  }
2181  
2182  /**
2183   * Validates a null value based on a schema.
2184   *
2185   * @since 5.7.0
2186   *
2187   * @param mixed  $value The value to validate.
2188   * @param string $param The parameter name, used in error messages.
2189   * @return true|WP_Error
2190   */
2191  function rest_validate_null_value_from_schema( $value, $param ) {
2192      if ( null !== $value ) {
2193          return new WP_Error(
2194              'rest_invalid_type',
2195              /* translators: 1: Parameter, 2: Type name. */
2196              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
2197              array( 'param' => $param )
2198          );
2199      }
2200  
2201      return true;
2202  }
2203  
2204  /**
2205   * Validates a boolean value based on a schema.
2206   *
2207   * @since 5.7.0
2208   *
2209   * @param mixed  $value The value to validate.
2210   * @param string $param The parameter name, used in error messages.
2211   * @return true|WP_Error
2212   */
2213  function rest_validate_boolean_value_from_schema( $value, $param ) {
2214      if ( ! rest_is_boolean( $value ) ) {
2215          return new WP_Error(
2216              'rest_invalid_type',
2217              /* translators: 1: Parameter, 2: Type name. */
2218              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
2219              array( 'param' => $param )
2220          );
2221      }
2222  
2223      return true;
2224  }
2225  
2226  /**
2227   * Validates an object value based on a schema.
2228   *
2229   * @since 5.7.0
2230   *
2231   * @param mixed  $value The value to validate.
2232   * @param array  $args  Schema array to use for validation.
2233   * @param string $param The parameter name, used in error messages.
2234   * @return true|WP_Error
2235   */
2236  function rest_validate_object_value_from_schema( $value, $args, $param ) {
2237      if ( ! rest_is_object( $value ) ) {
2238          return new WP_Error(
2239              'rest_invalid_type',
2240              /* translators: 1: Parameter, 2: Type name. */
2241              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
2242              array( 'param' => $param )
2243          );
2244      }
2245  
2246      $value = rest_sanitize_object( $value );
2247  
2248      if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
2249          foreach ( $args['required'] as $name ) {
2250              if ( ! array_key_exists( $name, $value ) ) {
2251                  return new WP_Error(
2252                      'rest_property_required',
2253                      /* translators: 1: Property of an object, 2: Parameter. */
2254                      sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2255                  );
2256              }
2257          }
2258      } elseif ( isset( $args['properties'] ) ) { // schema version 3
2259          foreach ( $args['properties'] as $name => $property ) {
2260              if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
2261                  return new WP_Error(
2262                      'rest_property_required',
2263                      /* translators: 1: Property of an object, 2: Parameter. */
2264                      sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2265                  );
2266              }
2267          }
2268      }
2269  
2270      foreach ( $value as $property => $v ) {
2271          if ( isset( $args['properties'][ $property ] ) ) {
2272              $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2273              if ( is_wp_error( $is_valid ) ) {
2274                  return $is_valid;
2275              }
2276              continue;
2277          }
2278  
2279          $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2280          if ( null !== $pattern_property_schema ) {
2281              $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2282              if ( is_wp_error( $is_valid ) ) {
2283                  return $is_valid;
2284              }
2285              continue;
2286          }
2287  
2288          if ( isset( $args['additionalProperties'] ) ) {
2289              if ( false === $args['additionalProperties'] ) {
2290                  return new WP_Error(
2291                      'rest_additional_properties_forbidden',
2292                      /* translators: %s: Property of an object. */
2293                      sprintf( __( '%1$s is not a valid property of Object.' ), $property )
2294                  );
2295              }
2296  
2297              if ( is_array( $args['additionalProperties'] ) ) {
2298                  $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2299                  if ( is_wp_error( $is_valid ) ) {
2300                      return $is_valid;
2301                  }
2302              }
2303          }
2304      }
2305  
2306      if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
2307          return new WP_Error(
2308              'rest_too_few_properties',
2309              sprintf(
2310                  /* translators: 1: Parameter, 2: Number. */
2311                  _n(
2312                      '%1$s must contain at least %2$s property.',
2313                      '%1$s must contain at least %2$s properties.',
2314                      $args['minProperties']
2315                  ),
2316                  $param,
2317                  number_format_i18n( $args['minProperties'] )
2318              )
2319          );
2320      }
2321  
2322      if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
2323          return new WP_Error(
2324              'rest_too_many_properties',
2325              sprintf(
2326                  /* translators: 1: Parameter, 2: Number. */
2327                  _n(
2328                      '%1$s must contain at most %2$s property.',
2329                      '%1$s must contain at most %2$s properties.',
2330                      $args['maxProperties']
2331                  ),
2332                  $param,
2333                  number_format_i18n( $args['maxProperties'] )
2334              )
2335          );
2336      }
2337  
2338      return true;
2339  }
2340  
2341  /**
2342   * Validates an array value based on a schema.
2343   *
2344   * @since 5.7.0
2345   *
2346   * @param mixed  $value The value to validate.
2347   * @param array  $args  Schema array to use for validation.
2348   * @param string $param The parameter name, used in error messages.
2349   * @return true|WP_Error
2350   */
2351  function rest_validate_array_value_from_schema( $value, $args, $param ) {
2352      if ( ! rest_is_array( $value ) ) {
2353          return new WP_Error(
2354              'rest_invalid_type',
2355              /* translators: 1: Parameter, 2: Type name. */
2356              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
2357              array( 'param' => $param )
2358          );
2359      }
2360  
2361      $value = rest_sanitize_array( $value );
2362  
2363      if ( isset( $args['items'] ) ) {
2364          foreach ( $value as $index => $v ) {
2365              $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2366              if ( is_wp_error( $is_valid ) ) {
2367                  return $is_valid;
2368              }
2369          }
2370      }
2371  
2372      if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
2373          return new WP_Error(
2374              'rest_too_few_items',
2375              sprintf(
2376                  /* translators: 1: Parameter, 2: Number. */
2377                  _n(
2378                      '%1$s must contain at least %2$s item.',
2379                      '%1$s must contain at least %2$s items.',
2380                      $args['minItems']
2381                  ),
2382                  $param,
2383                  number_format_i18n( $args['minItems'] )
2384              )
2385          );
2386      }
2387  
2388      if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
2389          return new WP_Error(
2390              'rest_too_many_items',
2391              sprintf(
2392                  /* translators: 1: Parameter, 2: Number. */
2393                  _n(
2394                      '%1$s must contain at most %2$s item.',
2395                      '%1$s must contain at most %2$s items.',
2396                      $args['maxItems']
2397                  ),
2398                  $param,
2399                  number_format_i18n( $args['maxItems'] )
2400              )
2401          );
2402      }
2403  
2404      if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2405          /* translators: %s: Parameter. */
2406          return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2407      }
2408  
2409      return true;
2410  }
2411  
2412  /**
2413   * Validates a number value based on a schema.
2414   *
2415   * @since 5.7.0
2416   *
2417   * @param mixed  $value The value to validate.
2418   * @param array  $args  Schema array to use for validation.
2419   * @param string $param The parameter name, used in error messages.
2420   * @return true|WP_Error
2421   */
2422  function rest_validate_number_value_from_schema( $value, $args, $param ) {
2423      if ( ! is_numeric( $value ) ) {
2424          return new WP_Error(
2425              'rest_invalid_type',
2426              /* translators: 1: Parameter, 2: Type name. */
2427              sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
2428              array( 'param' => $param )
2429          );
2430      }
2431  
2432      if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
2433          return new WP_Error(
2434              'rest_invalid_multiple',
2435              /* translators: 1: Parameter, 2: Multiplier. */
2436              sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] )
2437          );
2438      }
2439  
2440      if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
2441          if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
2442              return new WP_Error(
2443                  'rest_out_of_bounds',
2444                  /* translators: 1: Parameter, 2: Minimum number. */
2445                  sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] )
2446              );
2447          }
2448  
2449          if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
2450              return new WP_Error(
2451                  'rest_out_of_bounds',
2452                  /* translators: 1: Parameter, 2: Minimum number. */
2453                  sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] )
2454              );
2455          }
2456      }
2457  
2458      if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
2459          if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
2460              return new WP_Error(
2461                  'rest_out_of_bounds',
2462                  /* translators: 1: Parameter, 2: Maximum number. */
2463                  sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] )
2464              );
2465          }
2466  
2467          if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
2468              return new WP_Error(
2469                  'rest_out_of_bounds',
2470                  /* translators: 1: Parameter, 2: Maximum number. */
2471                  sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] )
2472              );
2473          }
2474      }
2475  
2476      if ( isset( $args['minimum'], $args['maximum'] ) ) {
2477          if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
2478              if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
2479                  return new WP_Error(
2480                      'rest_out_of_bounds',
2481                      sprintf(
2482                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2483                          __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ),
2484                          $param,
2485                          $args['minimum'],
2486                          $args['maximum']
2487                      )
2488                  );
2489              }
2490          }
2491  
2492          if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2493              if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
2494                  return new WP_Error(
2495                      'rest_out_of_bounds',
2496                      sprintf(
2497                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2498                          __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ),
2499                          $param,
2500                          $args['minimum'],
2501                          $args['maximum']
2502                      )
2503                  );
2504              }
2505          }
2506  
2507          if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) {
2508              if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
2509                  return new WP_Error(
2510                      'rest_out_of_bounds',
2511                      sprintf(
2512                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2513                          __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ),
2514                          $param,
2515                          $args['minimum'],
2516                          $args['maximum']
2517                      )
2518                  );
2519              }
2520          }
2521  
2522          if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2523              if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
2524                  return new WP_Error(
2525                      'rest_out_of_bounds',
2526                      sprintf(
2527                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2528                          __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ),
2529                          $param,
2530                          $args['minimum'],
2531                          $args['maximum']
2532                      )
2533                  );
2534              }
2535          }
2536      }
2537  
2538      return true;
2539  }
2540  
2541  /**
2542   * Validates a string value based on a schema.
2543   *
2544   * @since 5.7.0
2545   *
2546   * @param mixed  $value The value to validate.
2547   * @param array  $args  Schema array to use for validation.
2548   * @param string $param The parameter name, used in error messages.
2549   * @return true|WP_Error
2550   */
2551  function rest_validate_string_value_from_schema( $value, $args, $param ) {
2552      if ( ! is_string( $value ) ) {
2553          return new WP_Error(
2554              'rest_invalid_type',
2555              /* translators: 1: Parameter, 2: Type name. */
2556              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
2557              array( 'param' => $param )
2558          );
2559      }
2560  
2561      if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
2562          return new WP_Error(
2563              'rest_too_short',
2564              sprintf(
2565                  /* translators: 1: Parameter, 2: Number of characters. */
2566                  _n(
2567                      '%1$s must be at least %2$s character long.',
2568                      '%1$s must be at least %2$s characters long.',
2569                      $args['minLength']
2570                  ),
2571                  $param,
2572                  number_format_i18n( $args['minLength'] )
2573              )
2574          );
2575      }
2576  
2577      if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
2578          return new WP_Error(
2579              'rest_too_long',
2580              sprintf(
2581                  /* translators: 1: Parameter, 2: Number of characters. */
2582                  _n(
2583                      '%1$s must be at most %2$s character long.',
2584                      '%1$s must be at most %2$s characters long.',
2585                      $args['maxLength']
2586                  ),
2587                  $param,
2588                  number_format_i18n( $args['maxLength'] )
2589              )
2590          );
2591      }
2592  
2593      if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
2594          return new WP_Error(
2595              'rest_invalid_pattern',
2596              /* translators: 1: Parameter, 2: Pattern. */
2597              sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] )
2598          );
2599      }
2600  
2601      return true;
2602  }
2603  
2604  /**
2605   * Validates an integer value based on a schema.
2606   *
2607   * @since 5.7.0
2608   *
2609   * @param mixed  $value The value to validate.
2610   * @param array  $args  Schema array to use for validation.
2611   * @param string $param The parameter name, used in error messages.
2612   * @return true|WP_Error
2613   */
2614  function rest_validate_integer_value_from_schema( $value, $args, $param ) {
2615      $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param );
2616      if ( is_wp_error( $is_valid_number ) ) {
2617          return $is_valid_number;
2618      }
2619  
2620      if ( ! rest_is_integer( $value ) ) {
2621          return new WP_Error(
2622              'rest_invalid_type',
2623              /* translators: 1: Parameter, 2: Type name. */
2624              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
2625              array( 'param' => $param )
2626          );
2627      }
2628  
2629      return true;
2630  }
2631  
2632  /**
2633   * Sanitize a value based on a schema.
2634   *
2635   * @since 4.7.0
2636   * @since 5.5.0 Added the `$param` parameter.
2637   * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
2638   *
2639   * @param mixed  $value The value to sanitize.
2640   * @param array  $args  Schema array to use for sanitization.
2641   * @param string $param The parameter name, used in error messages.
2642   * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
2643   */
2644  function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
2645      if ( isset( $args['anyOf'] ) ) {
2646          $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2647          if ( is_wp_error( $matching_schema ) ) {
2648              return $matching_schema;
2649          }
2650  
2651          if ( ! isset( $args['type'] ) ) {
2652              $args['type'] = $matching_schema['type'];
2653          }
2654  
2655          $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2656      }
2657  
2658      if ( isset( $args['oneOf'] ) ) {
2659          $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2660          if ( is_wp_error( $matching_schema ) ) {
2661              return $matching_schema;
2662          }
2663  
2664          if ( ! isset( $args['type'] ) ) {
2665              $args['type'] = $matching_schema['type'];
2666          }
2667  
2668          $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2669      }
2670  
2671      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2672  
2673      if ( ! isset( $args['type'] ) ) {
2674          /* translators: %s: Parameter. */
2675          _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2676      }
2677  
2678      if ( is_array( $args['type'] ) ) {
2679          $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2680  
2681          if ( ! $best_type ) {
2682              return null;
2683          }
2684  
2685          $args['type'] = $best_type;
2686      }
2687  
2688      if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2689          _doing_it_wrong(
2690              __FUNCTION__,
2691              /* translators: 1: Parameter, 2: The list of allowed types. */
2692              wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2693              '5.5.0'
2694          );
2695      }
2696  
2697      if ( 'array' === $args['type'] ) {
2698          $value = rest_sanitize_array( $value );
2699  
2700          if ( ! empty( $args['items'] ) ) {
2701              foreach ( $value as $index => $v ) {
2702                  $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2703              }
2704          }
2705  
2706          if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2707              /* translators: %s: Parameter. */
2708              return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2709          }
2710  
2711          return $value;
2712      }
2713  
2714      if ( 'object' === $args['type'] ) {
2715          $value = rest_sanitize_object( $value );
2716  
2717          foreach ( $value as $property => $v ) {
2718              if ( isset( $args['properties'][ $property ] ) ) {
2719                  $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2720                  continue;
2721              }
2722  
2723              $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2724              if ( null !== $pattern_property_schema ) {
2725                  $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2726                  continue;
2727              }
2728  
2729              if ( isset( $args['additionalProperties'] ) ) {
2730                  if ( false === $args['additionalProperties'] ) {
2731                      unset( $value[ $property ] );
2732                  } elseif ( is_array( $args['additionalProperties'] ) ) {
2733                      $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2734                  }
2735              }
2736          }
2737  
2738          return $value;
2739      }
2740  
2741      if ( 'null' === $args['type'] ) {
2742          return null;
2743      }
2744  
2745      if ( 'integer' === $args['type'] ) {
2746          return (int) $value;
2747      }
2748  
2749      if ( 'number' === $args['type'] ) {
2750          return (float) $value;
2751      }
2752  
2753      if ( 'boolean' === $args['type'] ) {
2754          return rest_sanitize_boolean( $value );
2755      }
2756  
2757      // This behavior matches rest_validate_value_from_schema().
2758      if ( isset( $args['format'] )
2759          && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2760      ) {
2761          switch ( $args['format'] ) {
2762              case 'hex-color':
2763                  return (string) sanitize_hex_color( $value );
2764  
2765              case 'date-time':
2766                  return sanitize_text_field( $value );
2767  
2768              case 'email':
2769                  // sanitize_email() validates, which would be unexpected.
2770                  return sanitize_text_field( $value );
2771  
2772              case 'uri':
2773                  return esc_url_raw( $value );
2774  
2775              case 'ip':
2776                  return sanitize_text_field( $value );
2777  
2778              case 'uuid':
2779                  return sanitize_text_field( $value );
2780          }
2781      }
2782  
2783      if ( 'string' === $args['type'] ) {
2784          return (string) $value;
2785      }
2786  
2787      return $value;
2788  }
2789  
2790  /**
2791   * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
2792   * Expected to be called in the context of `array_reduce`.
2793   *
2794   * @since 5.0.0
2795   *
2796   * @param array  $memo Reduce accumulator.
2797   * @param string $path REST API path to preload.
2798   * @return array Modified reduce accumulator.
2799   */
2800  function rest_preload_api_request( $memo, $path ) {
2801      // array_reduce() doesn't support passing an array in PHP 5.2,
2802      // so we need to make sure we start with one.
2803      if ( ! is_array( $memo ) ) {
2804          $memo = array();
2805      }
2806  
2807      if ( empty( $path ) ) {
2808          return $memo;
2809      }
2810  
2811      $method = 'GET';
2812      if ( is_array( $path ) && 2 === count( $path ) ) {
2813          $method = end( $path );
2814          $path   = reset( $path );
2815  
2816          if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
2817              $method = 'GET';
2818          }
2819      }
2820  
2821      $path = untrailingslashit( $path );
2822      if ( empty( $path ) ) {
2823          $path = '/';
2824      }
2825  
2826      $path_parts = parse_url( $path );
2827      if ( false === $path_parts ) {
2828          return $memo;
2829      }
2830  
2831      $request = new WP_REST_Request( $method, $path_parts['path'] );
2832      if ( ! empty( $path_parts['query'] ) ) {
2833          parse_str( $path_parts['query'], $query_params );
2834          $request->set_query_params( $query_params );
2835      }
2836  
2837      $response = rest_do_request( $request );
2838      if ( 200 === $response->status ) {
2839          $server = rest_get_server();
2840          $embed  = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false;
2841          $data   = (array) $server->response_to_data( $response, $embed );
2842  
2843          if ( 'OPTIONS' === $method ) {
2844              $response = rest_send_allow_header( $response, $server, $request );
2845  
2846              $memo[ $method ][ $path ] = array(
2847                  'body'    => $data,
2848                  'headers' => $response->headers,
2849              );
2850          } else {
2851              $memo[ $path ] = array(
2852                  'body'    => $data,
2853                  'headers' => $response->headers,
2854              );
2855          }
2856      }
2857  
2858      return $memo;
2859  }
2860  
2861  /**
2862   * Parses the "_embed" parameter into the list of resources to embed.
2863   *
2864   * @since 5.4.0
2865   *
2866   * @param string|array $embed Raw "_embed" parameter value.
2867   * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
2868   */
2869  function rest_parse_embed_param( $embed ) {
2870      if ( ! $embed || 'true' === $embed || '1' === $embed ) {
2871          return true;
2872      }
2873  
2874      $rels = wp_parse_list( $embed );
2875  
2876      if ( ! $rels ) {
2877          return true;
2878      }
2879  
2880      return $rels;
2881  }
2882  
2883  /**
2884   * Filters the response to remove any fields not available in the given context.
2885   *
2886   * @since 5.5.0
2887   * @since 5.6.0 Support the "patternProperties" keyword for objects.
2888   *              Support the "anyOf" and "oneOf" keywords.
2889   *
2890   * @param array|object $data    The response data to modify.
2891   * @param array        $schema  The schema for the endpoint used to filter the response.
2892   * @param string       $context The requested context.
2893   * @return array|object The filtered response data.
2894   */
2895  function rest_filter_response_by_context( $data, $schema, $context ) {
2896      if ( isset( $schema['anyOf'] ) ) {
2897          $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
2898          if ( ! is_wp_error( $matching_schema ) ) {
2899              if ( ! isset( $schema['type'] ) ) {
2900                  $schema['type'] = $matching_schema['type'];
2901              }
2902  
2903              $data = rest_filter_response_by_context( $data, $matching_schema, $context );
2904          }
2905      }
2906  
2907      if ( isset( $schema['oneOf'] ) ) {
2908          $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
2909          if ( ! is_wp_error( $matching_schema ) ) {
2910              if ( ! isset( $schema['type'] ) ) {
2911                  $schema['type'] = $matching_schema['type'];
2912              }
2913  
2914              $data = rest_filter_response_by_context( $data, $matching_schema, $context );
2915          }
2916      }
2917  
2918      if ( ! is_array( $data ) && ! is_object( $data ) ) {
2919          return $data;
2920      }
2921  
2922      if ( isset( $schema['type'] ) ) {
2923          $type = $schema['type'];
2924      } elseif ( isset( $schema['properties'] ) ) {
2925          $type = 'object'; // Back compat if a developer accidentally omitted the type.
2926      } else {
2927          return $data;
2928      }
2929  
2930      $is_array_type  = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
2931      $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
2932  
2933      if ( $is_array_type && $is_object_type ) {
2934          if ( rest_is_array( $data ) ) {
2935              $is_object_type = false;
2936          } else {
2937              $is_array_type = false;
2938          }
2939      }
2940  
2941      $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
2942  
2943      foreach ( $data as $key => $value ) {
2944          $check = array();
2945  
2946          if ( $is_array_type ) {
2947              $check = isset( $schema['items'] ) ? $schema['items'] : array();
2948          } elseif ( $is_object_type ) {
2949              if ( isset( $schema['properties'][ $key ] ) ) {
2950                  $check = $schema['properties'][ $key ];
2951              } else {
2952                  $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
2953                  if ( null !== $pattern_property_schema ) {
2954                      $check = $pattern_property_schema;
2955                  } elseif ( $has_additional_properties ) {
2956                      $check = $schema['additionalProperties'];
2957                  }
2958              }
2959          }
2960  
2961          if ( ! isset( $check['context'] ) ) {
2962              continue;
2963          }
2964  
2965          if ( ! in_array( $context, $check['context'], true ) ) {
2966              if ( $is_array_type ) {
2967                  // All array items share schema, so there's no need to check each one.
2968                  $data = array();
2969                  break;
2970              }
2971  
2972              if ( is_object( $data ) ) {
2973                  unset( $data->$key );
2974              } else {
2975                  unset( $data[ $key ] );
2976              }
2977          } elseif ( is_array( $value ) || is_object( $value ) ) {
2978              $new_value = rest_filter_response_by_context( $value, $check, $context );
2979  
2980              if ( is_object( $data ) ) {
2981                  $data->$key = $new_value;
2982              } else {
2983                  $data[ $key ] = $new_value;
2984              }
2985          }
2986      }
2987  
2988      return $data;
2989  }
2990  
2991  /**
2992   * Sets the "additionalProperties" to false by default for all object definitions in the schema.
2993   *
2994   * @since 5.5.0
2995   * @since 5.6.0 Support the "patternProperties" keyword.
2996   *
2997   * @param array $schema The schema to modify.
2998   * @return array The modified schema.
2999   */
3000  function rest_default_additional_properties_to_false( $schema ) {
3001      $type = (array) $schema['type'];
3002  
3003      if ( in_array( 'object', $type, true ) ) {
3004          if ( isset( $schema['properties'] ) ) {
3005              foreach ( $schema['properties'] as $key => $child_schema ) {
3006                  $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3007              }
3008          }
3009  
3010          if ( isset( $schema['patternProperties'] ) ) {
3011              foreach ( $schema['patternProperties'] as $key => $child_schema ) {
3012                  $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3013              }
3014          }
3015  
3016          if ( ! isset( $schema['additionalProperties'] ) ) {
3017              $schema['additionalProperties'] = false;
3018          }
3019      }
3020  
3021      if ( in_array( 'array', $type, true ) ) {
3022          if ( isset( $schema['items'] ) ) {
3023              $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
3024          }
3025      }
3026  
3027      return $schema;
3028  }
3029  
3030  /**
3031   * Gets the REST API route for a post.
3032   *
3033   * @since 5.5.0
3034   *
3035   * @param int|WP_Post $post Post ID or post object.
3036   * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
3037   */
3038  function rest_get_route_for_post( $post ) {
3039      $post = get_post( $post );
3040  
3041      if ( ! $post instanceof WP_Post ) {
3042          return '';
3043      }
3044  
3045      $post_type = get_post_type_object( $post->post_type );
3046      if ( ! $post_type ) {
3047          return '';
3048      }
3049  
3050      $controller = $post_type->get_rest_controller();
3051      if ( ! $controller ) {
3052          return '';
3053      }
3054  
3055      $route = '';
3056  
3057      // The only two controllers that we can detect are the Attachments and Posts controllers.
3058      if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
3059          $namespace = 'wp/v2';
3060          $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
3061          $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
3062      }
3063  
3064      /**
3065       * Filters the REST API route for a post.
3066       *
3067       * @since 5.5.0
3068       *
3069       * @param string  $route The route path.
3070       * @param WP_Post $post  The post object.
3071       */
3072      return apply_filters( 'rest_route_for_post', $route, $post );
3073  }
3074  
3075  /**
3076   * Gets the REST API route for a term.
3077   *
3078   * @since 5.5.0
3079   *
3080   * @param int|WP_Term $term Term ID or term object.
3081   * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
3082   */
3083  function rest_get_route_for_term( $term ) {
3084      $term = get_term( $term );
3085  
3086      if ( ! $term instanceof WP_Term ) {
3087          return '';
3088      }
3089  
3090      $taxonomy = get_taxonomy( $term->taxonomy );
3091      if ( ! $taxonomy ) {
3092          return '';
3093      }
3094  
3095      $controller = $taxonomy->get_rest_controller();
3096      if ( ! $controller ) {
3097          return '';
3098      }
3099  
3100      $route = '';
3101  
3102      // The only controller that works is the Terms controller.
3103      if ( $controller instanceof WP_REST_Terms_Controller ) {
3104          $namespace = 'wp/v2';
3105          $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
3106          $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
3107      }
3108  
3109      /**
3110       * Filters the REST API route for a term.
3111       *
3112       * @since 5.5.0
3113       *
3114       * @param string  $route The route path.
3115       * @param WP_Term $term  The term object.
3116       */
3117      return apply_filters( 'rest_route_for_term', $route, $term );
3118  }
3119  
3120  /**
3121   * Gets the REST route for the currently queried object.
3122   *
3123   * @since 5.5.0
3124   *
3125   * @return string The REST route of the resource, or an empty string if no resource identified.
3126   */
3127  function rest_get_queried_resource_route() {
3128      if ( is_singular() ) {
3129          $route = rest_get_route_for_post( get_queried_object() );
3130      } elseif ( is_category() || is_tag() || is_tax() ) {
3131          $route = rest_get_route_for_term( get_queried_object() );
3132      } elseif ( is_author() ) {
3133          $route = '/wp/v2/users/' . get_queried_object_id();
3134      } else {
3135          $route = '';
3136      }
3137  
3138      /**
3139       * Filters the REST route for the currently queried object.
3140       *
3141       * @since 5.5.0
3142       *
3143       * @param string $link The route with a leading slash, or an empty string.
3144       */
3145      return apply_filters( 'rest_queried_resource_route', $route );
3146  }
3147  
3148  /**
3149   * Retrieves an array of endpoint arguments from the item schema and endpoint method.
3150   *
3151   * @since 5.6.0
3152   *
3153   * @param array  $schema The full JSON schema for the endpoint.
3154   * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
3155   *                       checked for required values and may fall-back to a given default, this is not done
3156   *                       on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
3157   * @return array The endpoint arguments.
3158   */
3159  function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
3160  
3161      $schema_properties       = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
3162      $endpoint_args           = array();
3163      $valid_schema_properties = rest_get_allowed_schema_keywords();
3164      $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
3165  
3166      foreach ( $schema_properties as $field_id => $params ) {
3167  
3168          // Arguments specified as `readonly` are not allowed to be set.
3169          if ( ! empty( $params['readonly'] ) ) {
3170              continue;
3171          }
3172  
3173          $endpoint_args[ $field_id ] = array(
3174              'validate_callback' => 'rest_validate_request_arg',
3175              'sanitize_callback' => 'rest_sanitize_request_arg',
3176          );
3177  
3178          if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
3179              $endpoint_args[ $field_id ]['default'] = $params['default'];
3180          }
3181  
3182          if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
3183              $endpoint_args[ $field_id ]['required'] = true;
3184          }
3185  
3186          foreach ( $valid_schema_properties as $schema_prop ) {
3187              if ( isset( $params[ $schema_prop ] ) ) {
3188                  $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
3189              }
3190          }
3191  
3192          // Merge in any options provided by the schema property.
3193          if ( isset( $params['arg_options'] ) ) {
3194  
3195              // Only use required / default from arg_options on CREATABLE endpoints.
3196              if ( WP_REST_Server::CREATABLE !== $method ) {
3197                  $params['arg_options'] = array_diff_key(
3198                      $params['arg_options'],
3199                      array(
3200                          'required' => '',
3201                          'default'  => '',
3202                      )
3203                  );
3204              }
3205  
3206              $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
3207          }
3208      }
3209  
3210      return $endpoint_args;
3211  }
3212  
3213  
3214  /**
3215   * Converts an error to a response object.
3216   *
3217   * This iterates over all error codes and messages to change it into a flat
3218   * array. This enables simpler client behaviour, as it is represented as a
3219   * list in JSON rather than an object/map.
3220   *
3221   * @since 5.7.0
3222   *
3223   * @param WP_Error $error WP_Error instance.
3224   *
3225   * @return WP_REST_Response List of associative arrays with code and message keys.
3226   */
3227  function rest_convert_error_to_response( $error ) {
3228      $status = array_reduce(
3229          $error->get_all_error_data(),
3230          static function ( $status, $error_data ) {
3231              return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status;
3232          },
3233          500
3234      );
3235  
3236      $errors = array();
3237  
3238      foreach ( (array) $error->errors as $code => $messages ) {
3239          $all_data  = $error->get_all_error_data( $code );
3240          $last_data = array_pop( $all_data );
3241  
3242          foreach ( (array) $messages as $message ) {
3243              $formatted = array(
3244                  'code'    => $code,
3245                  'message' => $message,
3246                  'data'    => $last_data,
3247              );
3248  
3249              if ( $all_data ) {
3250                  $formatted['additional_data'] = $all_data;
3251              }
3252  
3253              $errors[] = $formatted;
3254          }
3255      }
3256  
3257      $data = $errors[0];
3258      if ( count( $errors ) > 1 ) {
3259          // Remove the primary error.
3260          array_shift( $errors );
3261          $data['additional_errors'] = $errors;
3262      }
3263  
3264      return new WP_REST_Response( $data, $status );
3265  }


Generated: Thu Sep 23 01:00:04 2021 Cross-referenced by PHPXref 0.7.1