[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref