[ 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              // Remove the redundant preg_match() argument.
 822              unset( $args[0] );
 823  
 824              $request->set_url_params( $args );
 825              $request->set_attributes( $endpoint );
 826          }
 827  
 828          $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
 829          $response->set_matched_route( $route );
 830          break;
 831      }
 832  
 833      $response->set_data( $data );
 834      return $response;
 835  }
 836  
 837  /**
 838   * Sends the "Allow" header to state all methods that can be sent to the current route.
 839   *
 840   * @since 4.4.0
 841   *
 842   * @param WP_REST_Response $response Current response being served.
 843   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 844   * @param WP_REST_Request  $request  The request that was used to make current response.
 845   * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
 846   */
 847  function rest_send_allow_header( $response, $server, $request ) {
 848      $matched_route = $response->get_matched_route();
 849  
 850      if ( ! $matched_route ) {
 851          return $response;
 852      }
 853  
 854      $routes = $server->get_routes();
 855  
 856      $allowed_methods = array();
 857  
 858      // Get the allowed methods across the routes.
 859      foreach ( $routes[ $matched_route ] as $_handler ) {
 860          foreach ( $_handler['methods'] as $handler_method => $value ) {
 861  
 862              if ( ! empty( $_handler['permission_callback'] ) ) {
 863  
 864                  $permission = call_user_func( $_handler['permission_callback'], $request );
 865  
 866                  $allowed_methods[ $handler_method ] = true === $permission;
 867              } else {
 868                  $allowed_methods[ $handler_method ] = true;
 869              }
 870          }
 871      }
 872  
 873      // Strip out all the methods that are not allowed (false values).
 874      $allowed_methods = array_filter( $allowed_methods );
 875  
 876      if ( $allowed_methods ) {
 877          $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
 878      }
 879  
 880      return $response;
 881  }
 882  
 883  /**
 884   * Recursively computes the intersection of arrays using keys for comparison.
 885   *
 886   * @since 5.3.0
 887   *
 888   * @param array $array1 The array with master keys to check.
 889   * @param array $array2 An array to compare keys against.
 890   * @return array An associative array containing all the entries of array1 which have keys
 891   *               that are present in all arguments.
 892   */
 893  function _rest_array_intersect_key_recursive( $array1, $array2 ) {
 894      $array1 = array_intersect_key( $array1, $array2 );
 895      foreach ( $array1 as $key => $value ) {
 896          if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
 897              $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
 898          }
 899      }
 900      return $array1;
 901  }
 902  
 903  /**
 904   * Filters the REST API response to include only an allow-listed set of response object fields.
 905   *
 906   * @since 4.8.0
 907   *
 908   * @param WP_REST_Response $response Current response being served.
 909   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 910   * @param WP_REST_Request  $request  The request that was used to make current response.
 911   * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
 912   */
 913  function rest_filter_response_fields( $response, $server, $request ) {
 914      if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
 915          return $response;
 916      }
 917  
 918      $data = $response->get_data();
 919  
 920      $fields = wp_parse_list( $request['_fields'] );
 921  
 922      if ( 0 === count( $fields ) ) {
 923          return $response;
 924      }
 925  
 926      // Trim off outside whitespace from the comma delimited list.
 927      $fields = array_map( 'trim', $fields );
 928  
 929      // Create nested array of accepted field hierarchy.
 930      $fields_as_keyed = array();
 931      foreach ( $fields as $field ) {
 932          $parts = explode( '.', $field );
 933          $ref   = &$fields_as_keyed;
 934          while ( count( $parts ) > 1 ) {
 935              $next = array_shift( $parts );
 936              if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) {
 937                  // Skip any sub-properties if their parent prop is already marked for inclusion.
 938                  break 2;
 939              }
 940              $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array();
 941              $ref          = &$ref[ $next ];
 942          }
 943          $last         = array_shift( $parts );
 944          $ref[ $last ] = true;
 945      }
 946  
 947      if ( wp_is_numeric_array( $data ) ) {
 948          $new_data = array();
 949          foreach ( $data as $item ) {
 950              $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
 951          }
 952      } else {
 953          $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
 954      }
 955  
 956      $response->set_data( $new_data );
 957  
 958      return $response;
 959  }
 960  
 961  /**
 962   * Given an array of fields to include in a response, some of which may be
 963   * `nested.fields`, determine whether the provided field should be included
 964   * in the response body.
 965   *
 966   * If a parent field is passed in, the presence of any nested field within
 967   * that parent will cause the method to return `true`. For example "title"
 968   * will return true if any of `title`, `title.raw` or `title.rendered` is
 969   * provided.
 970   *
 971   * @since 5.3.0
 972   *
 973   * @param string $field  A field to test for inclusion in the response body.
 974   * @param array  $fields An array of string fields supported by the endpoint.
 975   * @return bool Whether to include the field or not.
 976   */
 977  function rest_is_field_included( $field, $fields ) {
 978      if ( in_array( $field, $fields, true ) ) {
 979          return true;
 980      }
 981  
 982      foreach ( $fields as $accepted_field ) {
 983          /*
 984           * Check to see if $field is the parent of any item in $fields.
 985           * A field "parent" should be accepted if "parent.child" is accepted.
 986           */
 987          if ( str_starts_with( $accepted_field, "$field." ) ) {
 988              return true;
 989          }
 990          /*
 991           * Conversely, if "parent" is accepted, all "parent.child" fields
 992           * should also be accepted.
 993           */
 994          if ( str_starts_with( $field, "$accepted_field." ) ) {
 995              return true;
 996          }
 997      }
 998  
 999      return false;
1000  }
1001  
1002  /**
1003   * Adds the REST API URL to the WP RSD endpoint.
1004   *
1005   * @since 4.4.0
1006   *
1007   * @see get_rest_url()
1008   */
1009  function rest_output_rsd() {
1010      $api_root = get_rest_url();
1011  
1012      if ( empty( $api_root ) ) {
1013          return;
1014      }
1015      ?>
1016      <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
1017      <?php
1018  }
1019  
1020  /**
1021   * Outputs the REST API link tag into page header.
1022   *
1023   * @since 4.4.0
1024   *
1025   * @see get_rest_url()
1026   */
1027  function rest_output_link_wp_head() {
1028      $api_root = get_rest_url();
1029  
1030      if ( empty( $api_root ) ) {
1031          return;
1032      }
1033  
1034      printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
1035  
1036      $resource = rest_get_queried_resource_route();
1037  
1038      if ( $resource ) {
1039          printf(
1040              '<link rel="alternate" title="%1$s" type="application/json" href="%2$s" />',
1041              _x( 'JSON', 'REST API resource link name' ),
1042              esc_url( rest_url( $resource ) )
1043          );
1044      }
1045  }
1046  
1047  /**
1048   * Sends a Link header for the REST API.
1049   *
1050   * @since 4.4.0
1051   */
1052  function rest_output_link_header() {
1053      if ( headers_sent() ) {
1054          return;
1055      }
1056  
1057      $api_root = get_rest_url();
1058  
1059      if ( empty( $api_root ) ) {
1060          return;
1061      }
1062  
1063      header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', sanitize_url( $api_root ) ), false );
1064  
1065      $resource = rest_get_queried_resource_route();
1066  
1067      if ( $resource ) {
1068          header(
1069              sprintf(
1070                  'Link: <%1$s>; rel="alternate"; title="%2$s"; type="application/json"',
1071                  sanitize_url( rest_url( $resource ) ),
1072                  _x( 'JSON', 'REST API resource link name' )
1073              ),
1074              false
1075          );
1076      }
1077  }
1078  
1079  /**
1080   * Checks for errors when using cookie-based authentication.
1081   *
1082   * WordPress' built-in cookie authentication is always active
1083   * for logged in users. However, the API has to check nonces
1084   * for each request to ensure users are not vulnerable to CSRF.
1085   *
1086   * @since 4.4.0
1087   *
1088   * @global mixed          $wp_rest_auth_cookie
1089   *
1090   * @param WP_Error|mixed $result Error from another authentication handler,
1091   *                               null if we should handle it, or another value if not.
1092   * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
1093   */
1094  function rest_cookie_check_errors( $result ) {
1095      if ( ! empty( $result ) ) {
1096          return $result;
1097      }
1098  
1099      global $wp_rest_auth_cookie;
1100  
1101      /*
1102       * Is cookie authentication being used? (If we get an auth
1103       * error, but we're still logged in, another authentication
1104       * must have been used).
1105       */
1106      if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
1107          return $result;
1108      }
1109  
1110      // Determine if there is a nonce.
1111      $nonce = null;
1112  
1113      if ( isset( $_REQUEST['_wpnonce'] ) ) {
1114          $nonce = $_REQUEST['_wpnonce'];
1115      } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
1116          $nonce = $_SERVER['HTTP_X_WP_NONCE'];
1117      }
1118  
1119      if ( null === $nonce ) {
1120          // No nonce at all, so act as if it's an unauthenticated request.
1121          wp_set_current_user( 0 );
1122          return true;
1123      }
1124  
1125      // Check the nonce.
1126      $result = wp_verify_nonce( $nonce, 'wp_rest' );
1127  
1128      if ( ! $result ) {
1129          add_filter( 'rest_send_nocache_headers', '__return_true', 20 );
1130          return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
1131      }
1132  
1133      // Send a refreshed nonce in header.
1134      rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
1135  
1136      return true;
1137  }
1138  
1139  /**
1140   * Collects cookie authentication status.
1141   *
1142   * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
1143   *
1144   * @since 4.4.0
1145   *
1146   * @see current_action()
1147   * @global mixed $wp_rest_auth_cookie
1148   */
1149  function rest_cookie_collect_status() {
1150      global $wp_rest_auth_cookie;
1151  
1152      $status_type = current_action();
1153  
1154      if ( 'auth_cookie_valid' !== $status_type ) {
1155          $wp_rest_auth_cookie = substr( $status_type, 12 );
1156          return;
1157      }
1158  
1159      $wp_rest_auth_cookie = true;
1160  }
1161  
1162  /**
1163   * Collects the status of authenticating with an application password.
1164   *
1165   * @since 5.6.0
1166   * @since 5.7.0 Added the `$app_password` parameter.
1167   *
1168   * @global WP_User|WP_Error|null $wp_rest_application_password_status
1169   * @global string|null $wp_rest_application_password_uuid
1170   *
1171   * @param WP_Error $user_or_error The authenticated user or error instance.
1172   * @param array    $app_password  The Application Password used to authenticate.
1173   */
1174  function rest_application_password_collect_status( $user_or_error, $app_password = array() ) {
1175      global $wp_rest_application_password_status, $wp_rest_application_password_uuid;
1176  
1177      $wp_rest_application_password_status = $user_or_error;
1178  
1179      if ( empty( $app_password['uuid'] ) ) {
1180          $wp_rest_application_password_uuid = null;
1181      } else {
1182          $wp_rest_application_password_uuid = $app_password['uuid'];
1183      }
1184  }
1185  
1186  /**
1187   * Gets the Application Password used for authenticating the request.
1188   *
1189   * @since 5.7.0
1190   *
1191   * @global string|null $wp_rest_application_password_uuid
1192   *
1193   * @return string|null The Application Password UUID, or null if Application Passwords was not used.
1194   */
1195  function rest_get_authenticated_app_password() {
1196      global $wp_rest_application_password_uuid;
1197  
1198      return $wp_rest_application_password_uuid;
1199  }
1200  
1201  /**
1202   * Checks for errors when using application password-based authentication.
1203   *
1204   * @since 5.6.0
1205   *
1206   * @global WP_User|WP_Error|null $wp_rest_application_password_status
1207   *
1208   * @param WP_Error|null|true $result Error from another authentication handler,
1209   *                                   null if we should handle it, or another value if not.
1210   * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true.
1211   */
1212  function rest_application_password_check_errors( $result ) {
1213      global $wp_rest_application_password_status;
1214  
1215      if ( ! empty( $result ) ) {
1216          return $result;
1217      }
1218  
1219      if ( is_wp_error( $wp_rest_application_password_status ) ) {
1220          $data = $wp_rest_application_password_status->get_error_data();
1221  
1222          if ( ! isset( $data['status'] ) ) {
1223              $data['status'] = 401;
1224          }
1225  
1226          $wp_rest_application_password_status->add_data( $data );
1227  
1228          return $wp_rest_application_password_status;
1229      }
1230  
1231      if ( $wp_rest_application_password_status instanceof WP_User ) {
1232          return true;
1233      }
1234  
1235      return $result;
1236  }
1237  
1238  /**
1239   * Adds Application Passwords info to the REST API index.
1240   *
1241   * @since 5.6.0
1242   *
1243   * @param WP_REST_Response $response The index response object.
1244   * @return WP_REST_Response
1245   */
1246  function rest_add_application_passwords_to_index( $response ) {
1247      if ( ! wp_is_application_passwords_available() ) {
1248          return $response;
1249      }
1250  
1251      $response->data['authentication']['application-passwords'] = array(
1252          'endpoints' => array(
1253              'authorization' => admin_url( 'authorize-application.php' ),
1254          ),
1255      );
1256  
1257      return $response;
1258  }
1259  
1260  /**
1261   * Retrieves the avatar URLs in various sizes.
1262   *
1263   * @since 4.7.0
1264   *
1265   * @see get_avatar_url()
1266   *
1267   * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar MD5 hash,
1268   *                           user email, WP_User object, WP_Post object, or WP_Comment object.
1269   * @return (string|false)[] Avatar URLs keyed by size. Each value can be a URL string or boolean false.
1270   */
1271  function rest_get_avatar_urls( $id_or_email ) {
1272      $avatar_sizes = rest_get_avatar_sizes();
1273  
1274      $urls = array();
1275      foreach ( $avatar_sizes as $size ) {
1276          $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
1277      }
1278  
1279      return $urls;
1280  }
1281  
1282  /**
1283   * Retrieves the pixel sizes for avatars.
1284   *
1285   * @since 4.7.0
1286   *
1287   * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
1288   */
1289  function rest_get_avatar_sizes() {
1290      /**
1291       * Filters the REST avatar sizes.
1292       *
1293       * Use this filter to adjust the array of sizes returned by the
1294       * `rest_get_avatar_sizes` function.
1295       *
1296       * @since 4.4.0
1297       *
1298       * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
1299       *                     Default `[ 24, 48, 96 ]`.
1300       */
1301      return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
1302  }
1303  
1304  /**
1305   * Parses an RFC3339 time into a Unix timestamp.
1306   *
1307   * Explicitly check for `false` to detect failure, as zero is a valid return
1308   * value on success.
1309   *
1310   * @since 4.4.0
1311   *
1312   * @param string $date      RFC3339 timestamp.
1313   * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
1314   *                          the timestamp's timezone. Default false.
1315   * @return int|false Unix timestamp on success, false on failure.
1316   */
1317  function rest_parse_date( $date, $force_utc = false ) {
1318      if ( $force_utc ) {
1319          $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
1320      }
1321  
1322      $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
1323  
1324      if ( ! preg_match( $regex, $date, $matches ) ) {
1325          return false;
1326      }
1327  
1328      return strtotime( $date );
1329  }
1330  
1331  /**
1332   * Parses a 3 or 6 digit hex color (with #).
1333   *
1334   * @since 5.4.0
1335   *
1336   * @param string $color 3 or 6 digit hex color (with #).
1337   * @return string|false Color value on success, false on failure.
1338   */
1339  function rest_parse_hex_color( $color ) {
1340      $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|';
1341      if ( ! preg_match( $regex, $color, $matches ) ) {
1342          return false;
1343      }
1344  
1345      return $color;
1346  }
1347  
1348  /**
1349   * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
1350   *
1351   * @since 4.4.0
1352   *
1353   * @see rest_parse_date()
1354   *
1355   * @param string $date   RFC3339 timestamp.
1356   * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
1357   * @return array|null {
1358   *     Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
1359   *     null on failure.
1360   *
1361   *     @type string $0 Local datetime string.
1362   *     @type string $1 UTC datetime string.
1363   * }
1364   */
1365  function rest_get_date_with_gmt( $date, $is_utc = false ) {
1366      /*
1367       * Whether or not the original date actually has a timezone string
1368       * changes the way we need to do timezone conversion.
1369       * Store this info before parsing the date, and use it later.
1370       */
1371      $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
1372  
1373      $date = rest_parse_date( $date );
1374  
1375      if ( false === $date ) {
1376          return null;
1377      }
1378  
1379      /*
1380       * At this point $date could either be a local date (if we were passed
1381       * a *local* date without a timezone offset) or a UTC date (otherwise).
1382       * Timezone conversion needs to be handled differently between these two cases.
1383       */
1384      if ( ! $is_utc && ! $has_timezone ) {
1385          $local = gmdate( 'Y-m-d H:i:s', $date );
1386          $utc   = get_gmt_from_date( $local );
1387      } else {
1388          $utc   = gmdate( 'Y-m-d H:i:s', $date );
1389          $local = get_date_from_gmt( $utc );
1390      }
1391  
1392      return array( $local, $utc );
1393  }
1394  
1395  /**
1396   * Returns a contextual HTTP error code for authorization failure.
1397   *
1398   * @since 4.7.0
1399   *
1400   * @return int 401 if the user is not logged in, 403 if the user is logged in.
1401   */
1402  function rest_authorization_required_code() {
1403      return is_user_logged_in() ? 403 : 401;
1404  }
1405  
1406  /**
1407   * Validate a request argument based on details registered to the route.
1408   *
1409   * @since 4.7.0
1410   *
1411   * @param mixed           $value
1412   * @param WP_REST_Request $request
1413   * @param string          $param
1414   * @return true|WP_Error
1415   */
1416  function rest_validate_request_arg( $value, $request, $param ) {
1417      $attributes = $request->get_attributes();
1418      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1419          return true;
1420      }
1421      $args = $attributes['args'][ $param ];
1422  
1423      return rest_validate_value_from_schema( $value, $args, $param );
1424  }
1425  
1426  /**
1427   * Sanitize a request argument based on details registered to the route.
1428   *
1429   * @since 4.7.0
1430   *
1431   * @param mixed           $value
1432   * @param WP_REST_Request $request
1433   * @param string          $param
1434   * @return mixed
1435   */
1436  function rest_sanitize_request_arg( $value, $request, $param ) {
1437      $attributes = $request->get_attributes();
1438      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1439          return $value;
1440      }
1441      $args = $attributes['args'][ $param ];
1442  
1443      return rest_sanitize_value_from_schema( $value, $args, $param );
1444  }
1445  
1446  /**
1447   * Parse a request argument based on details registered to the route.
1448   *
1449   * Runs a validation check and sanitizes the value, primarily to be used via
1450   * the `sanitize_callback` arguments in the endpoint args registration.
1451   *
1452   * @since 4.7.0
1453   *
1454   * @param mixed           $value
1455   * @param WP_REST_Request $request
1456   * @param string          $param
1457   * @return mixed
1458   */
1459  function rest_parse_request_arg( $value, $request, $param ) {
1460      $is_valid = rest_validate_request_arg( $value, $request, $param );
1461  
1462      if ( is_wp_error( $is_valid ) ) {
1463          return $is_valid;
1464      }
1465  
1466      $value = rest_sanitize_request_arg( $value, $request, $param );
1467  
1468      return $value;
1469  }
1470  
1471  /**
1472   * Determines if an IP address is valid.
1473   *
1474   * Handles both IPv4 and IPv6 addresses.
1475   *
1476   * @since 4.7.0
1477   *
1478   * @param string $ip IP address.
1479   * @return string|false The valid IP address, otherwise false.
1480   */
1481  function rest_is_ip_address( $ip ) {
1482      $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]?)$/';
1483  
1484      if ( ! preg_match( $ipv4_pattern, $ip ) && ! WpOrg\Requests\Ipv6::check_ipv6( $ip ) ) {
1485          return false;
1486      }
1487  
1488      return $ip;
1489  }
1490  
1491  /**
1492   * Changes a boolean-like value into the proper boolean value.
1493   *
1494   * @since 4.7.0
1495   *
1496   * @param bool|string|int $value The value being evaluated.
1497   * @return bool Returns the proper associated boolean value.
1498   */
1499  function rest_sanitize_boolean( $value ) {
1500      // String values are translated to `true`; make sure 'false' is false.
1501      if ( is_string( $value ) ) {
1502          $value = strtolower( $value );
1503          if ( in_array( $value, array( 'false', '0' ), true ) ) {
1504              $value = false;
1505          }
1506      }
1507  
1508      // Everything else will map nicely to boolean.
1509      return (bool) $value;
1510  }
1511  
1512  /**
1513   * Determines if a given value is boolean-like.
1514   *
1515   * @since 4.7.0
1516   *
1517   * @param bool|string $maybe_bool The value being evaluated.
1518   * @return bool True if a boolean, otherwise false.
1519   */
1520  function rest_is_boolean( $maybe_bool ) {
1521      if ( is_bool( $maybe_bool ) ) {
1522          return true;
1523      }
1524  
1525      if ( is_string( $maybe_bool ) ) {
1526          $maybe_bool = strtolower( $maybe_bool );
1527  
1528          $valid_boolean_values = array(
1529              'false',
1530              'true',
1531              '0',
1532              '1',
1533          );
1534  
1535          return in_array( $maybe_bool, $valid_boolean_values, true );
1536      }
1537  
1538      if ( is_int( $maybe_bool ) ) {
1539          return in_array( $maybe_bool, array( 0, 1 ), true );
1540      }
1541  
1542      return false;
1543  }
1544  
1545  /**
1546   * Determines if a given value is integer-like.
1547   *
1548   * @since 5.5.0
1549   *
1550   * @param mixed $maybe_integer The value being evaluated.
1551   * @return bool True if an integer, otherwise false.
1552   */
1553  function rest_is_integer( $maybe_integer ) {
1554      return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer;
1555  }
1556  
1557  /**
1558   * Determines if a given value is array-like.
1559   *
1560   * @since 5.5.0
1561   *
1562   * @param mixed $maybe_array The value being evaluated.
1563   * @return bool
1564   */
1565  function rest_is_array( $maybe_array ) {
1566      if ( is_scalar( $maybe_array ) ) {
1567          $maybe_array = wp_parse_list( $maybe_array );
1568      }
1569  
1570      return wp_is_numeric_array( $maybe_array );
1571  }
1572  
1573  /**
1574   * Converts an array-like value to an array.
1575   *
1576   * @since 5.5.0
1577   *
1578   * @param mixed $maybe_array The value being evaluated.
1579   * @return array Returns the array extracted from the value.
1580   */
1581  function rest_sanitize_array( $maybe_array ) {
1582      if ( is_scalar( $maybe_array ) ) {
1583          return wp_parse_list( $maybe_array );
1584      }
1585  
1586      if ( ! is_array( $maybe_array ) ) {
1587          return array();
1588      }
1589  
1590      // Normalize to numeric array so nothing unexpected is in the keys.
1591      return array_values( $maybe_array );
1592  }
1593  
1594  /**
1595   * Determines if a given value is object-like.
1596   *
1597   * @since 5.5.0
1598   *
1599   * @param mixed $maybe_object The value being evaluated.
1600   * @return bool True if object like, otherwise false.
1601   */
1602  function rest_is_object( $maybe_object ) {
1603      if ( '' === $maybe_object ) {
1604          return true;
1605      }
1606  
1607      if ( $maybe_object instanceof stdClass ) {
1608          return true;
1609      }
1610  
1611      if ( $maybe_object instanceof JsonSerializable ) {
1612          $maybe_object = $maybe_object->jsonSerialize();
1613      }
1614  
1615      return is_array( $maybe_object );
1616  }
1617  
1618  /**
1619   * Converts an object-like value to an array.
1620   *
1621   * @since 5.5.0
1622   *
1623   * @param mixed $maybe_object The value being evaluated.
1624   * @return array Returns the object extracted from the value as an associative array.
1625   */
1626  function rest_sanitize_object( $maybe_object ) {
1627      if ( '' === $maybe_object ) {
1628          return array();
1629      }
1630  
1631      if ( $maybe_object instanceof stdClass ) {
1632          return (array) $maybe_object;
1633      }
1634  
1635      if ( $maybe_object instanceof JsonSerializable ) {
1636          $maybe_object = $maybe_object->jsonSerialize();
1637      }
1638  
1639      if ( ! is_array( $maybe_object ) ) {
1640          return array();
1641      }
1642  
1643      return $maybe_object;
1644  }
1645  
1646  /**
1647   * Gets the best type for a value.
1648   *
1649   * @since 5.5.0
1650   *
1651   * @param mixed    $value The value to check.
1652   * @param string[] $types The list of possible types.
1653   * @return string The best matching type, an empty string if no types match.
1654   */
1655  function rest_get_best_type_for_value( $value, $types ) {
1656      static $checks = array(
1657          'array'   => 'rest_is_array',
1658          'object'  => 'rest_is_object',
1659          'integer' => 'rest_is_integer',
1660          'number'  => 'is_numeric',
1661          'boolean' => 'rest_is_boolean',
1662          'string'  => 'is_string',
1663          'null'    => 'is_null',
1664      );
1665  
1666      /*
1667       * Both arrays and objects allow empty strings to be converted to their types.
1668       * But the best answer for this type is a string.
1669       */
1670      if ( '' === $value && in_array( 'string', $types, true ) ) {
1671          return 'string';
1672      }
1673  
1674      foreach ( $types as $type ) {
1675          if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
1676              return $type;
1677          }
1678      }
1679  
1680      return '';
1681  }
1682  
1683  /**
1684   * Handles getting the best type for a multi-type schema.
1685   *
1686   * This is a wrapper for {@see rest_get_best_type_for_value()} that handles
1687   * backward compatibility for schemas that use invalid types.
1688   *
1689   * @since 5.5.0
1690   *
1691   * @param mixed  $value The value to check.
1692   * @param array  $args  The schema array to use.
1693   * @param string $param The parameter name, used in error messages.
1694   * @return string
1695   */
1696  function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
1697      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
1698      $invalid_types = array_diff( $args['type'], $allowed_types );
1699  
1700      if ( $invalid_types ) {
1701          _doing_it_wrong(
1702              __FUNCTION__,
1703              /* translators: 1: Parameter, 2: List of allowed types. */
1704              wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
1705              '5.5.0'
1706          );
1707      }
1708  
1709      $best_type = rest_get_best_type_for_value( $value, $args['type'] );
1710  
1711      if ( ! $best_type ) {
1712          if ( ! $invalid_types ) {
1713              return '';
1714          }
1715  
1716          // Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
1717          $best_type = reset( $invalid_types );
1718      }
1719  
1720      return $best_type;
1721  }
1722  
1723  /**
1724   * Checks if an array is made up of unique items.
1725   *
1726   * @since 5.5.0
1727   *
1728   * @param array $input_array The array to check.
1729   * @return bool True if the array contains unique items, false otherwise.
1730   */
1731  function rest_validate_array_contains_unique_items( $input_array ) {
1732      $seen = array();
1733  
1734      foreach ( $input_array as $item ) {
1735          $stabilized = rest_stabilize_value( $item );
1736          $key        = serialize( $stabilized );
1737  
1738          if ( ! isset( $seen[ $key ] ) ) {
1739              $seen[ $key ] = true;
1740  
1741              continue;
1742          }
1743  
1744          return false;
1745      }
1746  
1747      return true;
1748  }
1749  
1750  /**
1751   * Stabilizes a value following JSON Schema semantics.
1752   *
1753   * For lists, order is preserved. For objects, properties are reordered alphabetically.
1754   *
1755   * @since 5.5.0
1756   *
1757   * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays.
1758   * @return mixed The stabilized value.
1759   */
1760  function rest_stabilize_value( $value ) {
1761      if ( is_scalar( $value ) || is_null( $value ) ) {
1762          return $value;
1763      }
1764  
1765      if ( is_object( $value ) ) {
1766          _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' );
1767  
1768          return $value;
1769      }
1770  
1771      ksort( $value );
1772  
1773      foreach ( $value as $k => $v ) {
1774          $value[ $k ] = rest_stabilize_value( $v );
1775      }
1776  
1777      return $value;
1778  }
1779  
1780  /**
1781   * Validates if the JSON Schema pattern matches a value.
1782   *
1783   * @since 5.6.0
1784   *
1785   * @param string $pattern The pattern to match against.
1786   * @param string $value   The value to check.
1787   * @return bool           True if the pattern matches the given value, false otherwise.
1788   */
1789  function rest_validate_json_schema_pattern( $pattern, $value ) {
1790      $escaped_pattern = str_replace( '#', '\\#', $pattern );
1791  
1792      return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
1793  }
1794  
1795  /**
1796   * Finds the schema for a property using the patternProperties keyword.
1797   *
1798   * @since 5.6.0
1799   *
1800   * @param string $property The property name to check.
1801   * @param array  $args     The schema array to use.
1802   * @return array|null      The schema of matching pattern property, or null if no patterns match.
1803   */
1804  function rest_find_matching_pattern_property_schema( $property, $args ) {
1805      if ( isset( $args['patternProperties'] ) ) {
1806          foreach ( $args['patternProperties'] as $pattern => $child_schema ) {
1807              if ( rest_validate_json_schema_pattern( $pattern, $property ) ) {
1808                  return $child_schema;
1809              }
1810          }
1811      }
1812  
1813      return null;
1814  }
1815  
1816  /**
1817   * Formats a combining operation error into a WP_Error object.
1818   *
1819   * @since 5.6.0
1820   *
1821   * @param string $param The parameter name.
1822   * @param array $error  The error details.
1823   * @return WP_Error
1824   */
1825  function rest_format_combining_operation_error( $param, $error ) {
1826      $position = $error['index'];
1827      $reason   = $error['error_object']->get_error_message();
1828  
1829      if ( isset( $error['schema']['title'] ) ) {
1830          $title = $error['schema']['title'];
1831  
1832          return new WP_Error(
1833              'rest_no_matching_schema',
1834              /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
1835              sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
1836              array( 'position' => $position )
1837          );
1838      }
1839  
1840      return new WP_Error(
1841          'rest_no_matching_schema',
1842          /* translators: 1: Parameter, 2: Reason. */
1843          sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
1844          array( 'position' => $position )
1845      );
1846  }
1847  
1848  /**
1849   * Gets the error of combining operation.
1850   *
1851   * @since 5.6.0
1852   *
1853   * @param array  $value  The value to validate.
1854   * @param string $param  The parameter name, used in error messages.
1855   * @param array  $errors The errors array, to search for possible error.
1856   * @return WP_Error      The combining operation error.
1857   */
1858  function rest_get_combining_operation_error( $value, $param, $errors ) {
1859      // If there is only one error, simply return it.
1860      if ( 1 === count( $errors ) ) {
1861          return rest_format_combining_operation_error( $param, $errors[0] );
1862      }
1863  
1864      // Filter out all errors related to type validation.
1865      $filtered_errors = array();
1866      foreach ( $errors as $error ) {
1867          $error_code = $error['error_object']->get_error_code();
1868          $error_data = $error['error_object']->get_error_data();
1869  
1870          if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
1871              $filtered_errors[] = $error;
1872          }
1873      }
1874  
1875      // If there is only one error left, simply return it.
1876      if ( 1 === count( $filtered_errors ) ) {
1877          return rest_format_combining_operation_error( $param, $filtered_errors[0] );
1878      }
1879  
1880      // If there are only errors related to object validation, try choosing the most appropriate one.
1881      if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
1882          $result = null;
1883          $number = 0;
1884  
1885          foreach ( $filtered_errors as $error ) {
1886              if ( isset( $error['schema']['properties'] ) ) {
1887                  $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
1888                  if ( $n > $number ) {
1889                      $result = $error;
1890                      $number = $n;
1891                  }
1892              }
1893          }
1894  
1895          if ( null !== $result ) {
1896              return rest_format_combining_operation_error( $param, $result );
1897          }
1898      }
1899  
1900      // If each schema has a title, include those titles in the error message.
1901      $schema_titles = array();
1902      foreach ( $errors as $error ) {
1903          if ( isset( $error['schema']['title'] ) ) {
1904              $schema_titles[] = $error['schema']['title'];
1905          }
1906      }
1907  
1908      if ( count( $schema_titles ) === count( $errors ) ) {
1909          /* translators: 1: Parameter, 2: Schema titles. */
1910          return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
1911      }
1912  
1913      /* translators: %s: Parameter. */
1914      return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) );
1915  }
1916  
1917  /**
1918   * Finds the matching schema among the "anyOf" schemas.
1919   *
1920   * @since 5.6.0
1921   *
1922   * @param mixed  $value   The value to validate.
1923   * @param array  $args    The schema array to use.
1924   * @param string $param   The parameter name, used in error messages.
1925   * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
1926   */
1927  function rest_find_any_matching_schema( $value, $args, $param ) {
1928      $errors = array();
1929  
1930      foreach ( $args['anyOf'] as $index => $schema ) {
1931          if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1932              $schema['type'] = $args['type'];
1933          }
1934  
1935          $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1936          if ( ! is_wp_error( $is_valid ) ) {
1937              return $schema;
1938          }
1939  
1940          $errors[] = array(
1941              'error_object' => $is_valid,
1942              'schema'       => $schema,
1943              'index'        => $index,
1944          );
1945      }
1946  
1947      return rest_get_combining_operation_error( $value, $param, $errors );
1948  }
1949  
1950  /**
1951   * Finds the matching schema among the "oneOf" schemas.
1952   *
1953   * @since 5.6.0
1954   *
1955   * @param mixed  $value                  The value to validate.
1956   * @param array  $args                   The schema array to use.
1957   * @param string $param                  The parameter name, used in error messages.
1958   * @param bool   $stop_after_first_match Optional. Whether the process should stop after the first successful match.
1959   * @return array|WP_Error                The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
1960   */
1961  function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
1962      $matching_schemas = array();
1963      $errors           = array();
1964  
1965      foreach ( $args['oneOf'] as $index => $schema ) {
1966          if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1967              $schema['type'] = $args['type'];
1968          }
1969  
1970          $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1971          if ( ! is_wp_error( $is_valid ) ) {
1972              if ( $stop_after_first_match ) {
1973                  return $schema;
1974              }
1975  
1976              $matching_schemas[] = array(
1977                  'schema_object' => $schema,
1978                  'index'         => $index,
1979              );
1980          } else {
1981              $errors[] = array(
1982                  'error_object' => $is_valid,
1983                  'schema'       => $schema,
1984                  'index'        => $index,
1985              );
1986          }
1987      }
1988  
1989      if ( ! $matching_schemas ) {
1990          return rest_get_combining_operation_error( $value, $param, $errors );
1991      }
1992  
1993      if ( count( $matching_schemas ) > 1 ) {
1994          $schema_positions = array();
1995          $schema_titles    = array();
1996  
1997          foreach ( $matching_schemas as $schema ) {
1998              $schema_positions[] = $schema['index'];
1999  
2000              if ( isset( $schema['schema_object']['title'] ) ) {
2001                  $schema_titles[] = $schema['schema_object']['title'];
2002              }
2003          }
2004  
2005          // If each schema has a title, include those titles in the error message.
2006          if ( count( $schema_titles ) === count( $matching_schemas ) ) {
2007              return new WP_Error(
2008                  'rest_one_of_multiple_matches',
2009                  /* translators: 1: Parameter, 2: Schema titles. */
2010                  wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
2011                  array( 'positions' => $schema_positions )
2012              );
2013          }
2014  
2015          return new WP_Error(
2016              'rest_one_of_multiple_matches',
2017              /* translators: %s: Parameter. */
2018              sprintf( __( '%s matches more than one of the expected formats.' ), $param ),
2019              array( 'positions' => $schema_positions )
2020          );
2021      }
2022  
2023      return $matching_schemas[0]['schema_object'];
2024  }
2025  
2026  /**
2027   * Checks the equality of two values, following JSON Schema semantics.
2028   *
2029   * Property order is ignored for objects.
2030   *
2031   * Values must have been previously sanitized/coerced to their native types.
2032   *
2033   * @since 5.7.0
2034   *
2035   * @param mixed $value1 The first value to check.
2036   * @param mixed $value2 The second value to check.
2037   * @return bool True if the values are equal or false otherwise.
2038   */
2039  function rest_are_values_equal( $value1, $value2 ) {
2040      if ( is_array( $value1 ) && is_array( $value2 ) ) {
2041          if ( count( $value1 ) !== count( $value2 ) ) {
2042              return false;
2043          }
2044  
2045          foreach ( $value1 as $index => $value ) {
2046              if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
2047                  return false;
2048              }
2049          }
2050  
2051          return true;
2052      }
2053  
2054      if ( is_int( $value1 ) && is_float( $value2 )
2055          || is_float( $value1 ) && is_int( $value2 )
2056      ) {
2057          return (float) $value1 === (float) $value2;
2058      }
2059  
2060      return $value1 === $value2;
2061  }
2062  
2063  /**
2064   * Validates that the given value is a member of the JSON Schema "enum".
2065   *
2066   * @since 5.7.0
2067   *
2068   * @param mixed  $value  The value to validate.
2069   * @param array  $args   The schema array to use.
2070   * @param string $param  The parameter name, used in error messages.
2071   * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
2072   */
2073  function rest_validate_enum( $value, $args, $param ) {
2074      $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
2075      if ( is_wp_error( $sanitized_value ) ) {
2076          return $sanitized_value;
2077      }
2078  
2079      foreach ( $args['enum'] as $enum_value ) {
2080          if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
2081              return true;
2082          }
2083      }
2084  
2085      $encoded_enum_values = array();
2086      foreach ( $args['enum'] as $enum_value ) {
2087          $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
2088      }
2089  
2090      if ( count( $encoded_enum_values ) === 1 ) {
2091          /* translators: 1: Parameter, 2: Valid values. */
2092          return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
2093      }
2094  
2095      /* translators: 1: Parameter, 2: List of valid values. */
2096      return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
2097  }
2098  
2099  /**
2100   * Get all valid JSON schema properties.
2101   *
2102   * @since 5.6.0
2103   *
2104   * @return string[] All valid JSON schema properties.
2105   */
2106  function rest_get_allowed_schema_keywords() {
2107      return array(
2108          'title',
2109          'description',
2110          'default',
2111          'type',
2112          'format',
2113          'enum',
2114          'items',
2115          'properties',
2116          'additionalProperties',
2117          'patternProperties',
2118          'minProperties',
2119          'maxProperties',
2120          'minimum',
2121          'maximum',
2122          'exclusiveMinimum',
2123          'exclusiveMaximum',
2124          'multipleOf',
2125          'minLength',
2126          'maxLength',
2127          'pattern',
2128          'minItems',
2129          'maxItems',
2130          'uniqueItems',
2131          'anyOf',
2132          'oneOf',
2133      );
2134  }
2135  
2136  /**
2137   * Validate a value based on a schema.
2138   *
2139   * @since 4.7.0
2140   * @since 4.9.0 Support the "object" type.
2141   * @since 5.2.0 Support validating "additionalProperties" against a schema.
2142   * @since 5.3.0 Support multiple types.
2143   * @since 5.4.0 Convert an empty string to an empty object.
2144   * @since 5.5.0 Add the "uuid" and "hex-color" formats.
2145   *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
2146   *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
2147   *              Validate required properties.
2148   * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
2149   *              Support the "multipleOf" keyword for numbers and integers.
2150   *              Support the "patternProperties" keyword for objects.
2151   *              Support the "anyOf" and "oneOf" keywords.
2152   *
2153   * @param mixed  $value The value to validate.
2154   * @param array  $args  Schema array to use for validation.
2155   * @param string $param The parameter name, used in error messages.
2156   * @return true|WP_Error
2157   */
2158  function rest_validate_value_from_schema( $value, $args, $param = '' ) {
2159      if ( isset( $args['anyOf'] ) ) {
2160          $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2161          if ( is_wp_error( $matching_schema ) ) {
2162              return $matching_schema;
2163          }
2164  
2165          if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2166              $args['type'] = $matching_schema['type'];
2167          }
2168      }
2169  
2170      if ( isset( $args['oneOf'] ) ) {
2171          $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2172          if ( is_wp_error( $matching_schema ) ) {
2173              return $matching_schema;
2174          }
2175  
2176          if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2177              $args['type'] = $matching_schema['type'];
2178          }
2179      }
2180  
2181      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2182  
2183      if ( ! isset( $args['type'] ) ) {
2184          /* translators: %s: Parameter. */
2185          _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2186      }
2187  
2188      if ( is_array( $args['type'] ) ) {
2189          $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2190  
2191          if ( ! $best_type ) {
2192              return new WP_Error(
2193                  'rest_invalid_type',
2194                  /* translators: 1: Parameter, 2: List of types. */
2195                  sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
2196                  array( 'param' => $param )
2197              );
2198          }
2199  
2200          $args['type'] = $best_type;
2201      }
2202  
2203      if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2204          _doing_it_wrong(
2205              __FUNCTION__,
2206              /* translators: 1: Parameter, 2: The list of allowed types. */
2207              wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2208              '5.5.0'
2209          );
2210      }
2211  
2212      switch ( $args['type'] ) {
2213          case 'null':
2214              $is_valid = rest_validate_null_value_from_schema( $value, $param );
2215              break;
2216          case 'boolean':
2217              $is_valid = rest_validate_boolean_value_from_schema( $value, $param );
2218              break;
2219          case 'object':
2220              $is_valid = rest_validate_object_value_from_schema( $value, $args, $param );
2221              break;
2222          case 'array':
2223              $is_valid = rest_validate_array_value_from_schema( $value, $args, $param );
2224              break;
2225          case 'number':
2226              $is_valid = rest_validate_number_value_from_schema( $value, $args, $param );
2227              break;
2228          case 'string':
2229              $is_valid = rest_validate_string_value_from_schema( $value, $args, $param );
2230              break;
2231          case 'integer':
2232              $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param );
2233              break;
2234          default:
2235              $is_valid = true;
2236              break;
2237      }
2238  
2239      if ( is_wp_error( $is_valid ) ) {
2240          return $is_valid;
2241      }
2242  
2243      if ( ! empty( $args['enum'] ) ) {
2244          $enum_contains_value = rest_validate_enum( $value, $args, $param );
2245          if ( is_wp_error( $enum_contains_value ) ) {
2246              return $enum_contains_value;
2247          }
2248      }
2249  
2250      /*
2251       * The "format" keyword should only be applied to strings. However, for backward compatibility,
2252       * we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
2253       */
2254      if ( isset( $args['format'] )
2255          && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2256      ) {
2257          switch ( $args['format'] ) {
2258              case 'hex-color':
2259                  if ( ! rest_parse_hex_color( $value ) ) {
2260                      return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
2261                  }
2262                  break;
2263  
2264              case 'date-time':
2265                  if ( false === rest_parse_date( $value ) ) {
2266                      return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
2267                  }
2268                  break;
2269  
2270              case 'email':
2271                  if ( ! is_email( $value ) ) {
2272                      return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
2273                  }
2274                  break;
2275              case 'ip':
2276                  if ( ! rest_is_ip_address( $value ) ) {
2277                      /* translators: %s: IP address. */
2278                      return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) );
2279                  }
2280                  break;
2281              case 'uuid':
2282                  if ( ! wp_is_uuid( $value ) ) {
2283                      /* translators: %s: The name of a JSON field expecting a valid UUID. */
2284                      return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
2285                  }
2286                  break;
2287          }
2288      }
2289  
2290      return true;
2291  }
2292  
2293  /**
2294   * Validates a null value based on a schema.
2295   *
2296   * @since 5.7.0
2297   *
2298   * @param mixed  $value The value to validate.
2299   * @param string $param The parameter name, used in error messages.
2300   * @return true|WP_Error
2301   */
2302  function rest_validate_null_value_from_schema( $value, $param ) {
2303      if ( null !== $value ) {
2304          return new WP_Error(
2305              'rest_invalid_type',
2306              /* translators: 1: Parameter, 2: Type name. */
2307              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
2308              array( 'param' => $param )
2309          );
2310      }
2311  
2312      return true;
2313  }
2314  
2315  /**
2316   * Validates a boolean value based on a schema.
2317   *
2318   * @since 5.7.0
2319   *
2320   * @param mixed  $value The value to validate.
2321   * @param string $param The parameter name, used in error messages.
2322   * @return true|WP_Error
2323   */
2324  function rest_validate_boolean_value_from_schema( $value, $param ) {
2325      if ( ! rest_is_boolean( $value ) ) {
2326          return new WP_Error(
2327              'rest_invalid_type',
2328              /* translators: 1: Parameter, 2: Type name. */
2329              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
2330              array( 'param' => $param )
2331          );
2332      }
2333  
2334      return true;
2335  }
2336  
2337  /**
2338   * Validates an object value based on a schema.
2339   *
2340   * @since 5.7.0
2341   *
2342   * @param mixed  $value The value to validate.
2343   * @param array  $args  Schema array to use for validation.
2344   * @param string $param The parameter name, used in error messages.
2345   * @return true|WP_Error
2346   */
2347  function rest_validate_object_value_from_schema( $value, $args, $param ) {
2348      if ( ! rest_is_object( $value ) ) {
2349          return new WP_Error(
2350              'rest_invalid_type',
2351              /* translators: 1: Parameter, 2: Type name. */
2352              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
2353              array( 'param' => $param )
2354          );
2355      }
2356  
2357      $value = rest_sanitize_object( $value );
2358  
2359      if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
2360          foreach ( $args['required'] as $name ) {
2361              if ( ! array_key_exists( $name, $value ) ) {
2362                  return new WP_Error(
2363                      'rest_property_required',
2364                      /* translators: 1: Property of an object, 2: Parameter. */
2365                      sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2366                  );
2367              }
2368          }
2369      } elseif ( isset( $args['properties'] ) ) { // schema version 3
2370          foreach ( $args['properties'] as $name => $property ) {
2371              if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
2372                  return new WP_Error(
2373                      'rest_property_required',
2374                      /* translators: 1: Property of an object, 2: Parameter. */
2375                      sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2376                  );
2377              }
2378          }
2379      }
2380  
2381      foreach ( $value as $property => $v ) {
2382          if ( isset( $args['properties'][ $property ] ) ) {
2383              $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2384              if ( is_wp_error( $is_valid ) ) {
2385                  return $is_valid;
2386              }
2387              continue;
2388          }
2389  
2390          $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2391          if ( null !== $pattern_property_schema ) {
2392              $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2393              if ( is_wp_error( $is_valid ) ) {
2394                  return $is_valid;
2395              }
2396              continue;
2397          }
2398  
2399          if ( isset( $args['additionalProperties'] ) ) {
2400              if ( false === $args['additionalProperties'] ) {
2401                  return new WP_Error(
2402                      'rest_additional_properties_forbidden',
2403                      /* translators: %s: Property of an object. */
2404                      sprintf( __( '%1$s is not a valid property of Object.' ), $property )
2405                  );
2406              }
2407  
2408              if ( is_array( $args['additionalProperties'] ) ) {
2409                  $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2410                  if ( is_wp_error( $is_valid ) ) {
2411                      return $is_valid;
2412                  }
2413              }
2414          }
2415      }
2416  
2417      if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
2418          return new WP_Error(
2419              'rest_too_few_properties',
2420              sprintf(
2421                  /* translators: 1: Parameter, 2: Number. */
2422                  _n(
2423                      '%1$s must contain at least %2$s property.',
2424                      '%1$s must contain at least %2$s properties.',
2425                      $args['minProperties']
2426                  ),
2427                  $param,
2428                  number_format_i18n( $args['minProperties'] )
2429              )
2430          );
2431      }
2432  
2433      if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
2434          return new WP_Error(
2435              'rest_too_many_properties',
2436              sprintf(
2437                  /* translators: 1: Parameter, 2: Number. */
2438                  _n(
2439                      '%1$s must contain at most %2$s property.',
2440                      '%1$s must contain at most %2$s properties.',
2441                      $args['maxProperties']
2442                  ),
2443                  $param,
2444                  number_format_i18n( $args['maxProperties'] )
2445              )
2446          );
2447      }
2448  
2449      return true;
2450  }
2451  
2452  /**
2453   * Validates an array value based on a schema.
2454   *
2455   * @since 5.7.0
2456   *
2457   * @param mixed  $value The value to validate.
2458   * @param array  $args  Schema array to use for validation.
2459   * @param string $param The parameter name, used in error messages.
2460   * @return true|WP_Error
2461   */
2462  function rest_validate_array_value_from_schema( $value, $args, $param ) {
2463      if ( ! rest_is_array( $value ) ) {
2464          return new WP_Error(
2465              'rest_invalid_type',
2466              /* translators: 1: Parameter, 2: Type name. */
2467              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
2468              array( 'param' => $param )
2469          );
2470      }
2471  
2472      $value = rest_sanitize_array( $value );
2473  
2474      if ( isset( $args['items'] ) ) {
2475          foreach ( $value as $index => $v ) {
2476              $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2477              if ( is_wp_error( $is_valid ) ) {
2478                  return $is_valid;
2479              }
2480          }
2481      }
2482  
2483      if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
2484          return new WP_Error(
2485              'rest_too_few_items',
2486              sprintf(
2487                  /* translators: 1: Parameter, 2: Number. */
2488                  _n(
2489                      '%1$s must contain at least %2$s item.',
2490                      '%1$s must contain at least %2$s items.',
2491                      $args['minItems']
2492                  ),
2493                  $param,
2494                  number_format_i18n( $args['minItems'] )
2495              )
2496          );
2497      }
2498  
2499      if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
2500          return new WP_Error(
2501              'rest_too_many_items',
2502              sprintf(
2503                  /* translators: 1: Parameter, 2: Number. */
2504                  _n(
2505                      '%1$s must contain at most %2$s item.',
2506                      '%1$s must contain at most %2$s items.',
2507                      $args['maxItems']
2508                  ),
2509                  $param,
2510                  number_format_i18n( $args['maxItems'] )
2511              )
2512          );
2513      }
2514  
2515      if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2516          /* translators: %s: Parameter. */
2517          return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2518      }
2519  
2520      return true;
2521  }
2522  
2523  /**
2524   * Validates a number value based on a schema.
2525   *
2526   * @since 5.7.0
2527   *
2528   * @param mixed  $value The value to validate.
2529   * @param array  $args  Schema array to use for validation.
2530   * @param string $param The parameter name, used in error messages.
2531   * @return true|WP_Error
2532   */
2533  function rest_validate_number_value_from_schema( $value, $args, $param ) {
2534      if ( ! is_numeric( $value ) ) {
2535          return new WP_Error(
2536              'rest_invalid_type',
2537              /* translators: 1: Parameter, 2: Type name. */
2538              sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
2539              array( 'param' => $param )
2540          );
2541      }
2542  
2543      if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
2544          return new WP_Error(
2545              'rest_invalid_multiple',
2546              /* translators: 1: Parameter, 2: Multiplier. */
2547              sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] )
2548          );
2549      }
2550  
2551      if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
2552          if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
2553              return new WP_Error(
2554                  'rest_out_of_bounds',
2555                  /* translators: 1: Parameter, 2: Minimum number. */
2556                  sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] )
2557              );
2558          }
2559  
2560          if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
2561              return new WP_Error(
2562                  'rest_out_of_bounds',
2563                  /* translators: 1: Parameter, 2: Minimum number. */
2564                  sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] )
2565              );
2566          }
2567      }
2568  
2569      if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
2570          if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
2571              return new WP_Error(
2572                  'rest_out_of_bounds',
2573                  /* translators: 1: Parameter, 2: Maximum number. */
2574                  sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] )
2575              );
2576          }
2577  
2578          if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
2579              return new WP_Error(
2580                  'rest_out_of_bounds',
2581                  /* translators: 1: Parameter, 2: Maximum number. */
2582                  sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] )
2583              );
2584          }
2585      }
2586  
2587      if ( isset( $args['minimum'], $args['maximum'] ) ) {
2588          if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
2589              if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
2590                  return new WP_Error(
2591                      'rest_out_of_bounds',
2592                      sprintf(
2593                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2594                          __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ),
2595                          $param,
2596                          $args['minimum'],
2597                          $args['maximum']
2598                      )
2599                  );
2600              }
2601          }
2602  
2603          if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2604              if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
2605                  return new WP_Error(
2606                      'rest_out_of_bounds',
2607                      sprintf(
2608                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2609                          __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ),
2610                          $param,
2611                          $args['minimum'],
2612                          $args['maximum']
2613                      )
2614                  );
2615              }
2616          }
2617  
2618          if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) {
2619              if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
2620                  return new WP_Error(
2621                      'rest_out_of_bounds',
2622                      sprintf(
2623                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2624                          __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ),
2625                          $param,
2626                          $args['minimum'],
2627                          $args['maximum']
2628                      )
2629                  );
2630              }
2631          }
2632  
2633          if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2634              if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
2635                  return new WP_Error(
2636                      'rest_out_of_bounds',
2637                      sprintf(
2638                          /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2639                          __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ),
2640                          $param,
2641                          $args['minimum'],
2642                          $args['maximum']
2643                      )
2644                  );
2645              }
2646          }
2647      }
2648  
2649      return true;
2650  }
2651  
2652  /**
2653   * Validates a string value based on a schema.
2654   *
2655   * @since 5.7.0
2656   *
2657   * @param mixed  $value The value to validate.
2658   * @param array  $args  Schema array to use for validation.
2659   * @param string $param The parameter name, used in error messages.
2660   * @return true|WP_Error
2661   */
2662  function rest_validate_string_value_from_schema( $value, $args, $param ) {
2663      if ( ! is_string( $value ) ) {
2664          return new WP_Error(
2665              'rest_invalid_type',
2666              /* translators: 1: Parameter, 2: Type name. */
2667              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
2668              array( 'param' => $param )
2669          );
2670      }
2671  
2672      if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
2673          return new WP_Error(
2674              'rest_too_short',
2675              sprintf(
2676                  /* translators: 1: Parameter, 2: Number of characters. */
2677                  _n(
2678                      '%1$s must be at least %2$s character long.',
2679                      '%1$s must be at least %2$s characters long.',
2680                      $args['minLength']
2681                  ),
2682                  $param,
2683                  number_format_i18n( $args['minLength'] )
2684              )
2685          );
2686      }
2687  
2688      if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
2689          return new WP_Error(
2690              'rest_too_long',
2691              sprintf(
2692                  /* translators: 1: Parameter, 2: Number of characters. */
2693                  _n(
2694                      '%1$s must be at most %2$s character long.',
2695                      '%1$s must be at most %2$s characters long.',
2696                      $args['maxLength']
2697                  ),
2698                  $param,
2699                  number_format_i18n( $args['maxLength'] )
2700              )
2701          );
2702      }
2703  
2704      if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
2705          return new WP_Error(
2706              'rest_invalid_pattern',
2707              /* translators: 1: Parameter, 2: Pattern. */
2708              sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] )
2709          );
2710      }
2711  
2712      return true;
2713  }
2714  
2715  /**
2716   * Validates an integer value based on a schema.
2717   *
2718   * @since 5.7.0
2719   *
2720   * @param mixed  $value The value to validate.
2721   * @param array  $args  Schema array to use for validation.
2722   * @param string $param The parameter name, used in error messages.
2723   * @return true|WP_Error
2724   */
2725  function rest_validate_integer_value_from_schema( $value, $args, $param ) {
2726      $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param );
2727      if ( is_wp_error( $is_valid_number ) ) {
2728          return $is_valid_number;
2729      }
2730  
2731      if ( ! rest_is_integer( $value ) ) {
2732          return new WP_Error(
2733              'rest_invalid_type',
2734              /* translators: 1: Parameter, 2: Type name. */
2735              sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
2736              array( 'param' => $param )
2737          );
2738      }
2739  
2740      return true;
2741  }
2742  
2743  /**
2744   * Sanitize a value based on a schema.
2745   *
2746   * @since 4.7.0
2747   * @since 5.5.0 Added the `$param` parameter.
2748   * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
2749   * @since 5.9.0 Added `text-field` and `textarea-field` formats.
2750   *
2751   * @param mixed  $value The value to sanitize.
2752   * @param array  $args  Schema array to use for sanitization.
2753   * @param string $param The parameter name, used in error messages.
2754   * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
2755   */
2756  function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
2757      if ( isset( $args['anyOf'] ) ) {
2758          $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2759          if ( is_wp_error( $matching_schema ) ) {
2760              return $matching_schema;
2761          }
2762  
2763          if ( ! isset( $args['type'] ) ) {
2764              $args['type'] = $matching_schema['type'];
2765          }
2766  
2767          $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2768      }
2769  
2770      if ( isset( $args['oneOf'] ) ) {
2771          $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2772          if ( is_wp_error( $matching_schema ) ) {
2773              return $matching_schema;
2774          }
2775  
2776          if ( ! isset( $args['type'] ) ) {
2777              $args['type'] = $matching_schema['type'];
2778          }
2779  
2780          $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2781      }
2782  
2783      $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2784  
2785      if ( ! isset( $args['type'] ) ) {
2786          /* translators: %s: Parameter. */
2787          _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2788      }
2789  
2790      if ( is_array( $args['type'] ) ) {
2791          $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2792  
2793          if ( ! $best_type ) {
2794              return null;
2795          }
2796  
2797          $args['type'] = $best_type;
2798      }
2799  
2800      if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2801          _doing_it_wrong(
2802              __FUNCTION__,
2803              /* translators: 1: Parameter, 2: The list of allowed types. */
2804              wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2805              '5.5.0'
2806          );
2807      }
2808  
2809      if ( 'array' === $args['type'] ) {
2810          $value = rest_sanitize_array( $value );
2811  
2812          if ( ! empty( $args['items'] ) ) {
2813              foreach ( $value as $index => $v ) {
2814                  $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2815              }
2816          }
2817  
2818          if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2819              /* translators: %s: Parameter. */
2820              return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2821          }
2822  
2823          return $value;
2824      }
2825  
2826      if ( 'object' === $args['type'] ) {
2827          $value = rest_sanitize_object( $value );
2828  
2829          foreach ( $value as $property => $v ) {
2830              if ( isset( $args['properties'][ $property ] ) ) {
2831                  $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2832                  continue;
2833              }
2834  
2835              $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2836              if ( null !== $pattern_property_schema ) {
2837                  $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2838                  continue;
2839              }
2840  
2841              if ( isset( $args['additionalProperties'] ) ) {
2842                  if ( false === $args['additionalProperties'] ) {
2843                      unset( $value[ $property ] );
2844                  } elseif ( is_array( $args['additionalProperties'] ) ) {
2845                      $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2846                  }
2847              }
2848          }
2849  
2850          return $value;
2851      }
2852  
2853      if ( 'null' === $args['type'] ) {
2854          return null;
2855      }
2856  
2857      if ( 'integer' === $args['type'] ) {
2858          return (int) $value;
2859      }
2860  
2861      if ( 'number' === $args['type'] ) {
2862          return (float) $value;
2863      }
2864  
2865      if ( 'boolean' === $args['type'] ) {
2866          return rest_sanitize_boolean( $value );
2867      }
2868  
2869      // This behavior matches rest_validate_value_from_schema().
2870      if ( isset( $args['format'] )
2871          && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2872      ) {
2873          switch ( $args['format'] ) {
2874              case 'hex-color':
2875                  return (string) sanitize_hex_color( $value );
2876  
2877              case 'date-time':
2878                  return sanitize_text_field( $value );
2879  
2880              case 'email':
2881                  // sanitize_email() validates, which would be unexpected.
2882                  return sanitize_text_field( $value );
2883  
2884              case 'uri':
2885                  return sanitize_url( $value );
2886  
2887              case 'ip':
2888                  return sanitize_text_field( $value );
2889  
2890              case 'uuid':
2891                  return sanitize_text_field( $value );
2892  
2893              case 'text-field':
2894                  return sanitize_text_field( $value );
2895  
2896              case 'textarea-field':
2897                  return sanitize_textarea_field( $value );
2898          }
2899      }
2900  
2901      if ( 'string' === $args['type'] ) {
2902          return (string) $value;
2903      }
2904  
2905      return $value;
2906  }
2907  
2908  /**
2909   * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
2910   * Expected to be called in the context of `array_reduce`.
2911   *
2912   * @since 5.0.0
2913   *
2914   * @param array  $memo Reduce accumulator.
2915   * @param string $path REST API path to preload.
2916   * @return array Modified reduce accumulator.
2917   */
2918  function rest_preload_api_request( $memo, $path ) {
2919      /*
2920       * array_reduce() doesn't support passing an array in PHP 5.2,
2921       * so we need to make sure we start with one.
2922       */
2923      if ( ! is_array( $memo ) ) {
2924          $memo = array();
2925      }
2926  
2927      if ( empty( $path ) ) {
2928          return $memo;
2929      }
2930  
2931      $method = 'GET';
2932      if ( is_array( $path ) && 2 === count( $path ) ) {
2933          $method = end( $path );
2934          $path   = reset( $path );
2935  
2936          if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
2937              $method = 'GET';
2938          }
2939      }
2940  
2941      $path = untrailingslashit( $path );
2942      if ( empty( $path ) ) {
2943          $path = '/';
2944      }
2945  
2946      $path_parts = parse_url( $path );
2947      if ( false === $path_parts ) {
2948          return $memo;
2949      }
2950  
2951      $request = new WP_REST_Request( $method, $path_parts['path'] );
2952      if ( ! empty( $path_parts['query'] ) ) {
2953          parse_str( $path_parts['query'], $query_params );
2954          $request->set_query_params( $query_params );
2955      }
2956  
2957      $response = rest_do_request( $request );
2958      if ( 200 === $response->status ) {
2959          $server = rest_get_server();
2960          /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
2961          $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $server, $request );
2962          $embed    = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false;
2963          $data     = (array) $server->response_to_data( $response, $embed );
2964  
2965          if ( 'OPTIONS' === $method ) {
2966              $memo[ $method ][ $path ] = array(
2967                  'body'    => $data,
2968                  'headers' => $response->headers,
2969              );
2970          } else {
2971              $memo[ $path ] = array(
2972                  'body'    => $data,
2973                  'headers' => $response->headers,
2974              );
2975          }
2976      }
2977  
2978      return $memo;
2979  }
2980  
2981  /**
2982   * Parses the "_embed" parameter into the list of resources to embed.
2983   *
2984   * @since 5.4.0
2985   *
2986   * @param string|array $embed Raw "_embed" parameter value.
2987   * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
2988   */
2989  function rest_parse_embed_param( $embed ) {
2990      if ( ! $embed || 'true' === $embed || '1' === $embed ) {
2991          return true;
2992      }
2993  
2994      $rels = wp_parse_list( $embed );
2995  
2996      if ( ! $rels ) {
2997          return true;
2998      }
2999  
3000      return $rels;
3001  }
3002  
3003  /**
3004   * Filters the response to remove any fields not available in the given context.
3005   *
3006   * @since 5.5.0
3007   * @since 5.6.0 Support the "patternProperties" keyword for objects.
3008   *              Support the "anyOf" and "oneOf" keywords.
3009   *
3010   * @param array|object $response_data The response data to modify.
3011   * @param array        $schema        The schema for the endpoint used to filter the response.
3012   * @param string       $context       The requested context.
3013   * @return array|object The filtered response data.
3014   */
3015  function rest_filter_response_by_context( $response_data, $schema, $context ) {
3016      if ( isset( $schema['anyOf'] ) ) {
3017          $matching_schema = rest_find_any_matching_schema( $response_data, $schema, '' );
3018          if ( ! is_wp_error( $matching_schema ) ) {
3019              if ( ! isset( $schema['type'] ) ) {
3020                  $schema['type'] = $matching_schema['type'];
3021              }
3022  
3023              $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context );
3024          }
3025      }
3026  
3027      if ( isset( $schema['oneOf'] ) ) {
3028          $matching_schema = rest_find_one_matching_schema( $response_data, $schema, '', true );
3029          if ( ! is_wp_error( $matching_schema ) ) {
3030              if ( ! isset( $schema['type'] ) ) {
3031                  $schema['type'] = $matching_schema['type'];
3032              }
3033  
3034              $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context );
3035          }
3036      }
3037  
3038      if ( ! is_array( $response_data ) && ! is_object( $response_data ) ) {
3039          return $response_data;
3040      }
3041  
3042      if ( isset( $schema['type'] ) ) {
3043          $type = $schema['type'];
3044      } elseif ( isset( $schema['properties'] ) ) {
3045          $type = 'object'; // Back compat if a developer accidentally omitted the type.
3046      } else {
3047          return $response_data;
3048      }
3049  
3050      $is_array_type  = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
3051      $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
3052  
3053      if ( $is_array_type && $is_object_type ) {
3054          if ( rest_is_array( $response_data ) ) {
3055              $is_object_type = false;
3056          } else {
3057              $is_array_type = false;
3058          }
3059      }
3060  
3061      $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
3062  
3063      foreach ( $response_data as $key => $value ) {
3064          $check = array();
3065  
3066          if ( $is_array_type ) {
3067              $check = isset( $schema['items'] ) ? $schema['items'] : array();
3068          } elseif ( $is_object_type ) {
3069              if ( isset( $schema['properties'][ $key ] ) ) {
3070                  $check = $schema['properties'][ $key ];
3071              } else {
3072                  $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
3073                  if ( null !== $pattern_property_schema ) {
3074                      $check = $pattern_property_schema;
3075                  } elseif ( $has_additional_properties ) {
3076                      $check = $schema['additionalProperties'];
3077                  }
3078              }
3079          }
3080  
3081          if ( ! isset( $check['context'] ) ) {
3082              continue;
3083          }
3084  
3085          if ( ! in_array( $context, $check['context'], true ) ) {
3086              if ( $is_array_type ) {
3087                  // All array items share schema, so there's no need to check each one.
3088                  $response_data = array();
3089                  break;
3090              }
3091  
3092              if ( is_object( $response_data ) ) {
3093                  unset( $response_data->$key );
3094              } else {
3095                  unset( $response_data[ $key ] );
3096              }
3097          } elseif ( is_array( $value ) || is_object( $value ) ) {
3098              $new_value = rest_filter_response_by_context( $value, $check, $context );
3099  
3100              if ( is_object( $response_data ) ) {
3101                  $response_data->$key = $new_value;
3102              } else {
3103                  $response_data[ $key ] = $new_value;
3104              }
3105          }
3106      }
3107  
3108      return $response_data;
3109  }
3110  
3111  /**
3112   * Sets the "additionalProperties" to false by default for all object definitions in the schema.
3113   *
3114   * @since 5.5.0
3115   * @since 5.6.0 Support the "patternProperties" keyword.
3116   *
3117   * @param array $schema The schema to modify.
3118   * @return array The modified schema.
3119   */
3120  function rest_default_additional_properties_to_false( $schema ) {
3121      $type = (array) $schema['type'];
3122  
3123      if ( in_array( 'object', $type, true ) ) {
3124          if ( isset( $schema['properties'] ) ) {
3125              foreach ( $schema['properties'] as $key => $child_schema ) {
3126                  $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3127              }
3128          }
3129  
3130          if ( isset( $schema['patternProperties'] ) ) {
3131              foreach ( $schema['patternProperties'] as $key => $child_schema ) {
3132                  $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3133              }
3134          }
3135  
3136          if ( ! isset( $schema['additionalProperties'] ) ) {
3137              $schema['additionalProperties'] = false;
3138          }
3139      }
3140  
3141      if ( in_array( 'array', $type, true ) ) {
3142          if ( isset( $schema['items'] ) ) {
3143              $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
3144          }
3145      }
3146  
3147      return $schema;
3148  }
3149  
3150  /**
3151   * Gets the REST API route for a post.
3152   *
3153   * @since 5.5.0
3154   *
3155   * @param int|WP_Post $post Post ID or post object.
3156   * @return string The route path with a leading slash for the given post,
3157   *                or an empty string if there is not a route.
3158   */
3159  function rest_get_route_for_post( $post ) {
3160      $post = get_post( $post );
3161  
3162      if ( ! $post instanceof WP_Post ) {
3163          return '';
3164      }
3165  
3166      $post_type_route = rest_get_route_for_post_type_items( $post->post_type );
3167      if ( ! $post_type_route ) {
3168          return '';
3169      }
3170  
3171      $route = sprintf( '%s/%d', $post_type_route, $post->ID );
3172  
3173      /**
3174       * Filters the REST API route for a post.
3175       *
3176       * @since 5.5.0
3177       *
3178       * @param string  $route The route path.
3179       * @param WP_Post $post  The post object.
3180       */
3181      return apply_filters( 'rest_route_for_post', $route, $post );
3182  }
3183  
3184  /**
3185   * Gets the REST API route for a post type.
3186   *
3187   * @since 5.9.0
3188   *
3189   * @param string $post_type The name of a registered post type.
3190   * @return string The route path with a leading slash for the given post type,
3191   *                or an empty string if there is not a route.
3192   */
3193  function rest_get_route_for_post_type_items( $post_type ) {
3194      $post_type = get_post_type_object( $post_type );
3195      if ( ! $post_type ) {
3196          return '';
3197      }
3198  
3199      if ( ! $post_type->show_in_rest ) {
3200          return '';
3201      }
3202  
3203      $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2';
3204      $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
3205      $route     = sprintf( '/%s/%s', $namespace, $rest_base );
3206  
3207      /**
3208       * Filters the REST API route for a post type.
3209       *
3210       * @since 5.9.0
3211       *
3212       * @param string       $route      The route path.
3213       * @param WP_Post_Type $post_type  The post type object.
3214       */
3215      return apply_filters( 'rest_route_for_post_type_items', $route, $post_type );
3216  }
3217  
3218  /**
3219   * Gets the REST API route for a term.
3220   *
3221   * @since 5.5.0
3222   *
3223   * @param int|WP_Term $term Term ID or term object.
3224   * @return string The route path with a leading slash for the given term,
3225   *                or an empty string if there is not a route.
3226   */
3227  function rest_get_route_for_term( $term ) {
3228      $term = get_term( $term );
3229  
3230      if ( ! $term instanceof WP_Term ) {
3231          return '';
3232      }
3233  
3234      $taxonomy_route = rest_get_route_for_taxonomy_items( $term->taxonomy );
3235      if ( ! $taxonomy_route ) {
3236          return '';
3237      }
3238  
3239      $route = sprintf( '%s/%d', $taxonomy_route, $term->term_id );
3240  
3241      /**
3242       * Filters the REST API route for a term.
3243       *
3244       * @since 5.5.0
3245       *
3246       * @param string  $route The route path.
3247       * @param WP_Term $term  The term object.
3248       */
3249      return apply_filters( 'rest_route_for_term', $route, $term );
3250  }
3251  
3252  /**
3253   * Gets the REST API route for a taxonomy.
3254   *
3255   * @since 5.9.0
3256   *
3257   * @param string $taxonomy Name of taxonomy.
3258   * @return string The route path with a leading slash for the given taxonomy.
3259   */
3260  function rest_get_route_for_taxonomy_items( $taxonomy ) {
3261      $taxonomy = get_taxonomy( $taxonomy );
3262      if ( ! $taxonomy ) {
3263          return '';
3264      }
3265  
3266      if ( ! $taxonomy->show_in_rest ) {
3267          return '';
3268      }
3269  
3270      $namespace = ! empty( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2';
3271      $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
3272      $route     = sprintf( '/%s/%s', $namespace, $rest_base );
3273  
3274      /**
3275       * Filters the REST API route for a taxonomy.
3276       *
3277       * @since 5.9.0
3278       *
3279       * @param string      $route    The route path.
3280       * @param WP_Taxonomy $taxonomy The taxonomy object.
3281       */
3282      return apply_filters( 'rest_route_for_taxonomy_items', $route, $taxonomy );
3283  }
3284  
3285  /**
3286   * Gets the REST route for the currently queried object.
3287   *
3288   * @since 5.5.0
3289   *
3290   * @return string The REST route of the resource, or an empty string if no resource identified.
3291   */
3292  function rest_get_queried_resource_route() {
3293      if ( is_singular() ) {
3294          $route = rest_get_route_for_post( get_queried_object() );
3295      } elseif ( is_category() || is_tag() || is_tax() ) {
3296          $route = rest_get_route_for_term( get_queried_object() );
3297      } elseif ( is_author() ) {
3298          $route = '/wp/v2/users/' . get_queried_object_id();
3299      } else {
3300          $route = '';
3301      }
3302  
3303      /**
3304       * Filters the REST route for the currently queried object.
3305       *
3306       * @since 5.5.0
3307       *
3308       * @param string $link The route with a leading slash, or an empty string.
3309       */
3310      return apply_filters( 'rest_queried_resource_route', $route );
3311  }
3312  
3313  /**
3314   * Retrieves an array of endpoint arguments from the item schema and endpoint method.
3315   *
3316   * @since 5.6.0
3317   *
3318   * @param array  $schema The full JSON schema for the endpoint.
3319   * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
3320   *                       checked for required values and may fall-back to a given default, this is not done
3321   *                       on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
3322   * @return array The endpoint arguments.
3323   */
3324  function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
3325  
3326      $schema_properties       = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
3327      $endpoint_args           = array();
3328      $valid_schema_properties = rest_get_allowed_schema_keywords();
3329      $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
3330  
3331      foreach ( $schema_properties as $field_id => $params ) {
3332  
3333          // Arguments specified as `readonly` are not allowed to be set.
3334          if ( ! empty( $params['readonly'] ) ) {
3335              continue;
3336          }
3337  
3338          $endpoint_args[ $field_id ] = array(
3339              'validate_callback' => 'rest_validate_request_arg',
3340              'sanitize_callback' => 'rest_sanitize_request_arg',
3341          );
3342  
3343          if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
3344              $endpoint_args[ $field_id ]['default'] = $params['default'];
3345          }
3346  
3347          if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
3348              $endpoint_args[ $field_id ]['required'] = true;
3349          }
3350  
3351          foreach ( $valid_schema_properties as $schema_prop ) {
3352              if ( isset( $params[ $schema_prop ] ) ) {
3353                  $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
3354              }
3355          }
3356  
3357          // Merge in any options provided by the schema property.
3358          if ( isset( $params['arg_options'] ) ) {
3359  
3360              // Only use required / default from arg_options on CREATABLE endpoints.
3361              if ( WP_REST_Server::CREATABLE !== $method ) {
3362                  $params['arg_options'] = array_diff_key(
3363                      $params['arg_options'],
3364                      array(
3365                          'required' => '',
3366                          'default'  => '',
3367                      )
3368                  );
3369              }
3370  
3371              $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
3372          }
3373      }
3374  
3375      return $endpoint_args;
3376  }
3377  
3378  
3379  /**
3380   * Converts an error to a response object.
3381   *
3382   * This iterates over all error codes and messages to change it into a flat
3383   * array. This enables simpler client behavior, as it is represented as a
3384   * list in JSON rather than an object/map.
3385   *
3386   * @since 5.7.0
3387   *
3388   * @param WP_Error $error WP_Error instance.
3389   *
3390   * @return WP_REST_Response List of associative arrays with code and message keys.
3391   */
3392  function rest_convert_error_to_response( $error ) {
3393      $status = array_reduce(
3394          $error->get_all_error_data(),
3395          static function ( $status, $error_data ) {
3396              return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status;
3397          },
3398          500
3399      );
3400  
3401      $errors = array();
3402  
3403      foreach ( (array) $error->errors as $code => $messages ) {
3404          $all_data  = $error->get_all_error_data( $code );
3405          $last_data = array_pop( $all_data );
3406  
3407          foreach ( (array) $messages as $message ) {
3408              $formatted = array(
3409                  'code'    => $code,
3410                  'message' => $message,
3411                  'data'    => $last_data,
3412              );
3413  
3414              if ( $all_data ) {
3415                  $formatted['additional_data'] = $all_data;
3416              }
3417  
3418              $errors[] = $formatted;
3419          }
3420      }
3421  
3422      $data = $errors[0];
3423      if ( count( $errors ) > 1 ) {
3424          // Remove the primary error.
3425          array_shift( $errors );
3426          $data['additional_errors'] = $errors;
3427      }
3428  
3429      return new WP_REST_Response( $data, $status );
3430  }
3431  
3432  /**
3433   * Checks whether a REST API endpoint request is currently being handled.
3434   *
3435   * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
3436   *
3437   * @since 6.5.0
3438   *
3439   * @global WP_REST_Server $wp_rest_server REST server instance.
3440   *
3441   * @return bool True if a REST endpoint request is currently being handled, false otherwise.
3442   */
3443  function wp_is_rest_endpoint() {
3444      /* @var WP_REST_Server $wp_rest_server */
3445      global $wp_rest_server;
3446  
3447      // Check whether this is a standalone REST request.
3448      $is_rest_endpoint = wp_is_serving_rest_request();
3449      if ( ! $is_rest_endpoint ) {
3450          // Otherwise, check whether an internal REST request is currently being handled.
3451          $is_rest_endpoint = isset( $wp_rest_server )
3452              && $wp_rest_server->is_dispatching();
3453      }
3454  
3455      /**
3456       * Filters whether a REST endpoint request is currently being handled.
3457       *
3458       * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
3459       *
3460       * @since 6.5.0
3461       *
3462       * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled.
3463       */
3464      return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint );
3465  }


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref