[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Thu Jan 28 01:00:03 2021 Cross-referenced by PHPXref 0.7.1