[ 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   *
  25   * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
  26   * @param string $route     The base URL for route you are adding.
  27   * @param array  $args      Optional. Either an array of options for the endpoint, or an array of arrays for
  28   *                          multiple methods. Default empty array.
  29   * @param bool   $override  Optional. If the route already exists, should we override it? True overrides,
  30   *                          false merges (with newer overriding if duplicate keys exist). Default false.
  31   * @return bool True on success, false on error.
  32   */
  33  function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
  34      if ( empty( $namespace ) ) {
  35          /*
  36           * Non-namespaced routes are not allowed, with the exception of the main
  37           * and namespace indexes. If you really need to register a
  38           * non-namespaced route, call `WP_REST_Server::register_route` directly.
  39           */
  40          _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
  41          return false;
  42      } elseif ( empty( $route ) ) {
  43          _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
  44          return false;
  45      }
  46  
  47      $clean_namespace = trim( $namespace, '/' );
  48  
  49      if ( $clean_namespace !== $namespace ) {
  50          _doing_it_wrong( __FUNCTION__, __( 'Namespace must not start or end with a slash.' ), '5.4.2' );
  51      }
  52  
  53      if ( ! did_action( 'rest_api_init' ) ) {
  54          _doing_it_wrong(
  55              'register_rest_route',
  56              sprintf(
  57                  /* translators: %s: rest_api_init */
  58                  __( 'REST API routes must be registered on the %s action.' ),
  59                  '<code>rest_api_init</code>'
  60              ),
  61              '5.1.0'
  62          );
  63      }
  64  
  65      if ( isset( $args['args'] ) ) {
  66          $common_args = $args['args'];
  67          unset( $args['args'] );
  68      } else {
  69          $common_args = array();
  70      }
  71  
  72      if ( isset( $args['callback'] ) ) {
  73          // Upgrade a single set to multiple.
  74          $args = array( $args );
  75      }
  76  
  77      $defaults = array(
  78          'methods'  => 'GET',
  79          'callback' => null,
  80          'args'     => array(),
  81      );
  82  
  83      foreach ( $args as $key => &$arg_group ) {
  84          if ( ! is_numeric( $key ) ) {
  85              // Route option, skip here.
  86              continue;
  87          }
  88  
  89          $arg_group         = array_merge( $defaults, $arg_group );
  90          $arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
  91      }
  92  
  93      $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' );
  94      rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override );
  95      return true;
  96  }
  97  
  98  /**
  99   * Registers a new field on an existing WordPress object type.
 100   *
 101   * @since 4.7.0
 102   *
 103   * @global array $wp_rest_additional_fields Holds registered fields, organized
 104   *                                          by object type.
 105   *
 106   * @param string|array $object_type Object(s) the field is being registered
 107   *                                  to, "post"|"term"|"comment" etc.
 108   * @param string $attribute         The attribute name.
 109   * @param array  $args {
 110   *     Optional. An array of arguments used to handle the registered field.
 111   *
 112   *     @type callable|null $get_callback    Optional. The callback function used to retrieve the field value. Default is
 113   *                                          'null', the field will not be returned in the response. The function will
 114   *                                          be passed the prepared object data.
 115   *     @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
 116   *                                          is 'null', the value cannot be set or updated. The function will be passed
 117   *                                          the model object, like WP_Post.
 118   *     @type array|null $schema             Optional. The callback function used to create the schema for this field.
 119   *                                          Default is 'null', no schema entry will be returned.
 120   * }
 121   */
 122  function register_rest_field( $object_type, $attribute, $args = array() ) {
 123      $defaults = array(
 124          'get_callback'    => null,
 125          'update_callback' => null,
 126          'schema'          => null,
 127      );
 128  
 129      $args = wp_parse_args( $args, $defaults );
 130  
 131      global $wp_rest_additional_fields;
 132  
 133      $object_types = (array) $object_type;
 134  
 135      foreach ( $object_types as $object_type ) {
 136          $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
 137      }
 138  }
 139  
 140  /**
 141   * Registers rewrite rules for the API.
 142   *
 143   * @since 4.4.0
 144   *
 145   * @see rest_api_register_rewrites()
 146   * @global WP $wp Current WordPress environment instance.
 147   */
 148  function rest_api_init() {
 149      rest_api_register_rewrites();
 150  
 151      global $wp;
 152      $wp->add_query_var( 'rest_route' );
 153  }
 154  
 155  /**
 156   * Adds REST rewrite rules.
 157   *
 158   * @since 4.4.0
 159   *
 160   * @see add_rewrite_rule()
 161   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 162   */
 163  function rest_api_register_rewrites() {
 164      global $wp_rewrite;
 165  
 166      add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
 167      add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
 168      add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
 169      add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
 170  }
 171  
 172  /**
 173   * Registers the default REST API filters.
 174   *
 175   * Attached to the {@see 'rest_api_init'} action
 176   * to make testing and disabling these filters easier.
 177   *
 178   * @since 4.4.0
 179   */
 180  function rest_api_default_filters() {
 181      // Deprecated reporting.
 182      add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
 183      add_filter( 'deprecated_function_trigger_error', '__return_false' );
 184      add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
 185      add_filter( 'deprecated_argument_trigger_error', '__return_false' );
 186  
 187      // Default serving.
 188      add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
 189      add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
 190      add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
 191  
 192      add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
 193  }
 194  
 195  /**
 196   * Registers default REST API routes.
 197   *
 198   * @since 4.7.0
 199   */
 200  function create_initial_rest_routes() {
 201      foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
 202          $controller = $post_type->get_rest_controller();
 203  
 204          if ( ! $controller ) {
 205              continue;
 206          }
 207  
 208          $controller->register_routes();
 209  
 210          if ( post_type_supports( $post_type->name, 'revisions' ) ) {
 211              $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
 212              $revisions_controller->register_routes();
 213          }
 214  
 215          if ( 'attachment' !== $post_type->name ) {
 216              $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
 217              $autosaves_controller->register_routes();
 218          }
 219      }
 220  
 221      // Post types.
 222      $controller = new WP_REST_Post_Types_Controller;
 223      $controller->register_routes();
 224  
 225      // Post statuses.
 226      $controller = new WP_REST_Post_Statuses_Controller;
 227      $controller->register_routes();
 228  
 229      // Taxonomies.
 230      $controller = new WP_REST_Taxonomies_Controller;
 231      $controller->register_routes();
 232  
 233      // Terms.
 234      foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
 235          $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
 236  
 237          if ( ! class_exists( $class ) ) {
 238              continue;
 239          }
 240          $controller = new $class( $taxonomy->name );
 241          if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
 242              continue;
 243          }
 244  
 245          $controller->register_routes();
 246      }
 247  
 248      // Users.
 249      $controller = new WP_REST_Users_Controller;
 250      $controller->register_routes();
 251  
 252      // Comments.
 253      $controller = new WP_REST_Comments_Controller;
 254      $controller->register_routes();
 255  
 256      /**
 257       * Filters the search handlers to use in the REST search controller.
 258       *
 259       * @since 5.0.0
 260       *
 261       * @param array $search_handlers List of search handlers to use in the controller. Each search
 262       *                               handler instance must extend the `WP_REST_Search_Handler` class.
 263       *                               Default is only a handler for posts.
 264       */
 265      $search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) );
 266  
 267      $controller = new WP_REST_Search_Controller( $search_handlers );
 268      $controller->register_routes();
 269  
 270      // Block Renderer.
 271      $controller = new WP_REST_Block_Renderer_Controller;
 272      $controller->register_routes();
 273  
 274      // Settings.
 275      $controller = new WP_REST_Settings_Controller;
 276      $controller->register_routes();
 277  
 278      // Themes.
 279      $controller = new WP_REST_Themes_Controller;
 280      $controller->register_routes();
 281  
 282  }
 283  
 284  /**
 285   * Loads the REST API.
 286   *
 287   * @since 4.4.0
 288   *
 289   * @global WP $wp Current WordPress environment instance.
 290   */
 291  function rest_api_loaded() {
 292      if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
 293          return;
 294      }
 295  
 296      /**
 297       * Whether this is a REST Request.
 298       *
 299       * @since 4.4.0
 300       * @var bool
 301       */
 302      define( 'REST_REQUEST', true );
 303  
 304      // Initialize the server.
 305      $server = rest_get_server();
 306  
 307      // Fire off the request.
 308      $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
 309      if ( empty( $route ) ) {
 310          $route = '/';
 311      }
 312      $server->serve_request( $route );
 313  
 314      // We're done.
 315      die();
 316  }
 317  
 318  /**
 319   * Retrieves the URL prefix for any API resource.
 320   *
 321   * @since 4.4.0
 322   *
 323   * @return string Prefix.
 324   */
 325  function rest_get_url_prefix() {
 326      /**
 327       * Filters the REST URL prefix.
 328       *
 329       * @since 4.4.0
 330       *
 331       * @param string $prefix URL prefix. Default 'wp-json'.
 332       */
 333      return apply_filters( 'rest_url_prefix', 'wp-json' );
 334  }
 335  
 336  /**
 337   * Retrieves the URL to a REST endpoint on a site.
 338   *
 339   * Note: The returned URL is NOT escaped.
 340   *
 341   * @since 4.4.0
 342   *
 343   * @todo Check if this is even necessary
 344   * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 345   *
 346   * @param int    $blog_id Optional. Blog ID. Default of null returns URL for current blog.
 347   * @param string $path    Optional. REST route. Default '/'.
 348   * @param string $scheme  Optional. Sanitization scheme. Default 'rest'.
 349   * @return string Full URL to the endpoint.
 350   */
 351  function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
 352      if ( empty( $path ) ) {
 353          $path = '/';
 354      }
 355  
 356      $path = '/' . ltrim( $path, '/' );
 357  
 358      if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
 359          global $wp_rewrite;
 360  
 361          if ( $wp_rewrite->using_index_permalinks() ) {
 362              $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
 363          } else {
 364              $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
 365          }
 366  
 367          $url .= $path;
 368      } else {
 369          $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
 370          // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php.
 371          // To work around this, we manually add index.php to the URL, avoiding the redirect.
 372          if ( 'index.php' !== substr( $url, 9 ) ) {
 373              $url .= 'index.php';
 374          }
 375  
 376          $url = add_query_arg( 'rest_route', $path, $url );
 377      }
 378  
 379      if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) {
 380          // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
 381          if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) {
 382              $url = set_url_scheme( $url, 'https' );
 383          }
 384      }
 385  
 386      if ( is_admin() && force_ssl_admin() ) {
 387          /*
 388           * In this situation the home URL may be http:, and `is_ssl()` may be false,
 389           * but the admin is served over https: (one way or another), so REST API usage
 390           * will be blocked by browsers unless it is also served over HTTPS.
 391           */
 392          $url = set_url_scheme( $url, 'https' );
 393      }
 394  
 395      /**
 396       * Filters the REST URL.
 397       *
 398       * Use this filter to adjust the url returned by the get_rest_url() function.
 399       *
 400       * @since 4.4.0
 401       *
 402       * @param string $url     REST URL.
 403       * @param string $path    REST route.
 404       * @param int    $blog_id Blog ID.
 405       * @param string $scheme  Sanitization scheme.
 406       */
 407      return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
 408  }
 409  
 410  /**
 411   * Retrieves the URL to a REST endpoint.
 412   *
 413   * Note: The returned URL is NOT escaped.
 414   *
 415   * @since 4.4.0
 416   *
 417   * @param string $path   Optional. REST route. Default empty.
 418   * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
 419   * @return string Full URL to the endpoint.
 420   */
 421  function rest_url( $path = '', $scheme = 'rest' ) {
 422      return get_rest_url( null, $path, $scheme );
 423  }
 424  
 425  /**
 426   * Do a REST request.
 427   *
 428   * Used primarily to route internal requests through WP_REST_Server.
 429   *
 430   * @since 4.4.0
 431   *
 432   * @param WP_REST_Request|string $request Request.
 433   * @return WP_REST_Response REST response.
 434   */
 435  function rest_do_request( $request ) {
 436      $request = rest_ensure_request( $request );
 437      return rest_get_server()->dispatch( $request );
 438  }
 439  
 440  /**
 441   * Retrieves the current REST server instance.
 442   *
 443   * Instantiates a new instance if none exists already.
 444   *
 445   * @since 4.5.0
 446   *
 447   * @global WP_REST_Server $wp_rest_server REST server instance.
 448   *
 449   * @return WP_REST_Server REST server instance.
 450   */
 451  function rest_get_server() {
 452      /* @var WP_REST_Server $wp_rest_server */
 453      global $wp_rest_server;
 454  
 455      if ( empty( $wp_rest_server ) ) {
 456          /**
 457           * Filters the REST Server Class.
 458           *
 459           * This filter allows you to adjust the server class used by the API, using a
 460           * different class to handle requests.
 461           *
 462           * @since 4.4.0
 463           *
 464           * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
 465           */
 466          $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
 467          $wp_rest_server       = new $wp_rest_server_class;
 468  
 469          /**
 470           * Fires when preparing to serve an API request.
 471           *
 472           * Endpoint objects should be created and register their hooks on this action rather
 473           * than another action to ensure they're only loaded when needed.
 474           *
 475           * @since 4.4.0
 476           *
 477           * @param WP_REST_Server $wp_rest_server Server object.
 478           */
 479          do_action( 'rest_api_init', $wp_rest_server );
 480      }
 481  
 482      return $wp_rest_server;
 483  }
 484  
 485  /**
 486   * Ensures request arguments are a request object (for consistency).
 487   *
 488   * @since 4.4.0
 489   * @since 5.3.0 Accept string argument for the request path.
 490   *
 491   * @param array|string|WP_REST_Request $request Request to check.
 492   * @return WP_REST_Request REST request instance.
 493   */
 494  function rest_ensure_request( $request ) {
 495      if ( $request instanceof WP_REST_Request ) {
 496          return $request;
 497      }
 498  
 499      if ( is_string( $request ) ) {
 500          return new WP_REST_Request( 'GET', $request );
 501      }
 502  
 503      return new WP_REST_Request( 'GET', '', $request );
 504  }
 505  
 506  /**
 507   * Ensures a REST response is a response object (for consistency).
 508   *
 509   * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc
 510   * without needing to double-check the object. Will also allow WP_Error to indicate error
 511   * responses, so users should immediately check for this value.
 512   *
 513   * @since 4.4.0
 514   *
 515   * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check.
 516   * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response
 517   *                                   is already an instance, WP_REST_Response, otherwise
 518   *                                   returns a new WP_REST_Response instance.
 519   */
 520  function rest_ensure_response( $response ) {
 521      if ( is_wp_error( $response ) ) {
 522          return $response;
 523      }
 524  
 525      if ( $response instanceof WP_REST_Response ) {
 526          return $response;
 527      }
 528  
 529      // While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide
 530      // all the required methods used in WP_REST_Server::dispatch().
 531      if ( $response instanceof WP_HTTP_Response ) {
 532          return new WP_REST_Response(
 533              $response->get_data(),
 534              $response->get_status(),
 535              $response->get_headers()
 536          );
 537      }
 538  
 539      return new WP_REST_Response( $response );
 540  }
 541  
 542  /**
 543   * Handles _deprecated_function() errors.
 544   *
 545   * @since 4.4.0
 546   *
 547   * @param string $function    The function that was called.
 548   * @param string $replacement The function that should have been called.
 549   * @param string $version     Version.
 550   */
 551  function rest_handle_deprecated_function( $function, $replacement, $version ) {
 552      if ( ! WP_DEBUG || headers_sent() ) {
 553          return;
 554      }
 555      if ( ! empty( $replacement ) ) {
 556          /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */
 557          $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
 558      } else {
 559          /* translators: 1: Function name, 2: WordPress version number. */
 560          $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
 561      }
 562  
 563      header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
 564  }
 565  
 566  /**
 567   * Handles _deprecated_argument() errors.
 568   *
 569   * @since 4.4.0
 570   *
 571   * @param string $function    The function that was called.
 572   * @param string $message     A message regarding the change.
 573   * @param string $version     Version.
 574   */
 575  function rest_handle_deprecated_argument( $function, $message, $version ) {
 576      if ( ! WP_DEBUG || headers_sent() ) {
 577          return;
 578      }
 579      if ( ! empty( $message ) ) {
 580          /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */
 581          $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
 582      } else {
 583          /* translators: 1: Function name, 2: WordPress version number. */
 584          $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
 585      }
 586  
 587      header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
 588  }
 589  
 590  /**
 591   * Sends Cross-Origin Resource Sharing headers with API requests.
 592   *
 593   * @since 4.4.0
 594   *
 595   * @param mixed $value Response data.
 596   * @return mixed Response data.
 597   */
 598  function rest_send_cors_headers( $value ) {
 599      $origin = get_http_origin();
 600  
 601      if ( $origin ) {
 602          // Requests from file:// and data: URLs send "Origin: null".
 603          if ( 'null' !== $origin ) {
 604              $origin = esc_url_raw( $origin );
 605          }
 606          header( 'Access-Control-Allow-Origin: ' . $origin );
 607          header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
 608          header( 'Access-Control-Allow-Credentials: true' );
 609          header( 'Vary: Origin', false );
 610      } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
 611          header( 'Vary: Origin', false );
 612      }
 613  
 614      return $value;
 615  }
 616  
 617  /**
 618   * Handles OPTIONS requests for the server.
 619   *
 620   * This is handled outside of the server code, as it doesn't obey normal route
 621   * mapping.
 622   *
 623   * @since 4.4.0
 624   *
 625   * @param mixed           $response Current response, either response or `null` to indicate pass-through.
 626   * @param WP_REST_Server  $handler  ResponseHandler instance (usually WP_REST_Server).
 627   * @param WP_REST_Request $request  The request that was used to make current response.
 628   * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
 629   */
 630  function rest_handle_options_request( $response, $handler, $request ) {
 631      if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
 632          return $response;
 633      }
 634  
 635      $response = new WP_REST_Response();
 636      $data     = array();
 637  
 638      foreach ( $handler->get_routes() as $route => $endpoints ) {
 639          $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches );
 640  
 641          if ( ! $match ) {
 642              continue;
 643          }
 644  
 645          $args = array();
 646          foreach ( $matches as $param => $value ) {
 647              if ( ! is_int( $param ) ) {
 648                  $args[ $param ] = $value;
 649              }
 650          }
 651  
 652          foreach ( $endpoints as $endpoint ) {
 653              // Remove the redundant preg_match() argument.
 654              unset( $args[0] );
 655  
 656              $request->set_url_params( $args );
 657              $request->set_attributes( $endpoint );
 658          }
 659  
 660          $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
 661          $response->set_matched_route( $route );
 662          break;
 663      }
 664  
 665      $response->set_data( $data );
 666      return $response;
 667  }
 668  
 669  /**
 670   * Sends the "Allow" header to state all methods that can be sent to the current route.
 671   *
 672   * @since 4.4.0
 673   *
 674   * @param WP_REST_Response $response Current response being served.
 675   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 676   * @param WP_REST_Request  $request  The request that was used to make current response.
 677   * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
 678   */
 679  function rest_send_allow_header( $response, $server, $request ) {
 680      $matched_route = $response->get_matched_route();
 681  
 682      if ( ! $matched_route ) {
 683          return $response;
 684      }
 685  
 686      $routes = $server->get_routes();
 687  
 688      $allowed_methods = array();
 689  
 690      // Get the allowed methods across the routes.
 691      foreach ( $routes[ $matched_route ] as $_handler ) {
 692          foreach ( $_handler['methods'] as $handler_method => $value ) {
 693  
 694              if ( ! empty( $_handler['permission_callback'] ) ) {
 695  
 696                  $permission = call_user_func( $_handler['permission_callback'], $request );
 697  
 698                  $allowed_methods[ $handler_method ] = true === $permission;
 699              } else {
 700                  $allowed_methods[ $handler_method ] = true;
 701              }
 702          }
 703      }
 704  
 705      // Strip out all the methods that are not allowed (false values).
 706      $allowed_methods = array_filter( $allowed_methods );
 707  
 708      if ( $allowed_methods ) {
 709          $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
 710      }
 711  
 712      return $response;
 713  }
 714  
 715  /**
 716   * Recursively computes the intersection of arrays using keys for comparison.
 717   *
 718   * @param  array $array1 The array with master keys to check.
 719   * @param  array $array2 An array to compare keys against.
 720   *
 721   * @return array An associative array containing all the entries of array1 which have keys that are present in all arguments.
 722   */
 723  function _rest_array_intersect_key_recursive( $array1, $array2 ) {
 724      $array1 = array_intersect_key( $array1, $array2 );
 725      foreach ( $array1 as $key => $value ) {
 726          if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
 727              $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
 728          }
 729      }
 730      return $array1;
 731  }
 732  
 733  /**
 734   * Filter the API response to include only a white-listed set of response object fields.
 735   *
 736   * @since 4.8.0
 737   *
 738   * @param WP_REST_Response $response Current response being served.
 739   * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
 740   * @param WP_REST_Request  $request  The request that was used to make current response.
 741   *
 742   * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
 743   */
 744  function rest_filter_response_fields( $response, $server, $request ) {
 745      if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
 746          return $response;
 747      }
 748  
 749      $data = $response->get_data();
 750  
 751      $fields = wp_parse_list( $request['_fields'] );
 752  
 753      if ( 0 === count( $fields ) ) {
 754          return $response;
 755      }
 756  
 757      // Trim off outside whitespace from the comma delimited list.
 758      $fields = array_map( 'trim', $fields );
 759  
 760      // Create nested array of accepted field hierarchy.
 761      $fields_as_keyed = array();
 762      foreach ( $fields as $field ) {
 763          $parts = explode( '.', $field );
 764          $ref   = &$fields_as_keyed;
 765          while ( count( $parts ) > 1 ) {
 766              $next = array_shift( $parts );
 767              if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) {
 768                  // Skip any sub-properties if their parent prop is already marked for inclusion.
 769                  break 2;
 770              }
 771              $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array();
 772              $ref          = &$ref[ $next ];
 773          }
 774          $last         = array_shift( $parts );
 775          $ref[ $last ] = true;
 776      }
 777  
 778      if ( wp_is_numeric_array( $data ) ) {
 779          $new_data = array();
 780          foreach ( $data as $item ) {
 781              $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
 782          }
 783      } else {
 784          $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
 785      }
 786  
 787      $response->set_data( $new_data );
 788  
 789      return $response;
 790  }
 791  
 792  /**
 793   * Given an array of fields to include in a response, some of which may be
 794   * `nested.fields`, determine whether the provided field should be included
 795   * in the response body.
 796   *
 797   * If a parent field is passed in, the presence of any nested field within
 798   * that parent will cause the method to return `true`. For example "title"
 799   * will return true if any of `title`, `title.raw` or `title.rendered` is
 800   * provided.
 801   *
 802   * @since 5.3.0
 803   *
 804   * @param string $field  A field to test for inclusion in the response body.
 805   * @param array  $fields An array of string fields supported by the endpoint.
 806   * @return bool Whether to include the field or not.
 807   */
 808  function rest_is_field_included( $field, $fields ) {
 809      if ( in_array( $field, $fields, true ) ) {
 810          return true;
 811      }
 812  
 813      foreach ( $fields as $accepted_field ) {
 814          // Check to see if $field is the parent of any item in $fields.
 815          // A field "parent" should be accepted if "parent.child" is accepted.
 816          if ( strpos( $accepted_field, "$field." ) === 0 ) {
 817              return true;
 818          }
 819          // Conversely, if "parent" is accepted, all "parent.child" fields
 820          // should also be accepted.
 821          if ( strpos( $field, "$accepted_field." ) === 0 ) {
 822              return true;
 823          }
 824      }
 825  
 826      return false;
 827  }
 828  
 829  /**
 830   * Adds the REST API URL to the WP RSD endpoint.
 831   *
 832   * @since 4.4.0
 833   *
 834   * @see get_rest_url()
 835   */
 836  function rest_output_rsd() {
 837      $api_root = get_rest_url();
 838  
 839      if ( empty( $api_root ) ) {
 840          return;
 841      }
 842      ?>
 843      <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
 844      <?php
 845  }
 846  
 847  /**
 848   * Outputs the REST API link tag into page header.
 849   *
 850   * @since 4.4.0
 851   *
 852   * @see get_rest_url()
 853   */
 854  function rest_output_link_wp_head() {
 855      $api_root = get_rest_url();
 856  
 857      if ( empty( $api_root ) ) {
 858          return;
 859      }
 860  
 861      echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
 862  }
 863  
 864  /**
 865   * Sends a Link header for the REST API.
 866   *
 867   * @since 4.4.0
 868   */
 869  function rest_output_link_header() {
 870      if ( headers_sent() ) {
 871          return;
 872      }
 873  
 874      $api_root = get_rest_url();
 875  
 876      if ( empty( $api_root ) ) {
 877          return;
 878      }
 879  
 880      header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
 881  }
 882  
 883  /**
 884   * Checks for errors when using cookie-based authentication.
 885   *
 886   * WordPress' built-in cookie authentication is always active
 887   * for logged in users. However, the API has to check nonces
 888   * for each request to ensure users are not vulnerable to CSRF.
 889   *
 890   * @since 4.4.0
 891   *
 892   * @global mixed          $wp_rest_auth_cookie
 893   *
 894   * @param WP_Error|mixed $result Error from another authentication handler,
 895   *                               null if we should handle it, or another value if not.
 896   * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
 897   */
 898  function rest_cookie_check_errors( $result ) {
 899      if ( ! empty( $result ) ) {
 900          return $result;
 901      }
 902  
 903      global $wp_rest_auth_cookie;
 904  
 905      /*
 906       * Is cookie authentication being used? (If we get an auth
 907       * error, but we're still logged in, another authentication
 908       * must have been used).
 909       */
 910      if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
 911          return $result;
 912      }
 913  
 914      // Determine if there is a nonce.
 915      $nonce = null;
 916  
 917      if ( isset( $_REQUEST['_wpnonce'] ) ) {
 918          $nonce = $_REQUEST['_wpnonce'];
 919      } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
 920          $nonce = $_SERVER['HTTP_X_WP_NONCE'];
 921      }
 922  
 923      if ( null === $nonce ) {
 924          // No nonce at all, so act as if it's an unauthenticated request.
 925          wp_set_current_user( 0 );
 926          return true;
 927      }
 928  
 929      // Check the nonce.
 930      $result = wp_verify_nonce( $nonce, 'wp_rest' );
 931  
 932      if ( ! $result ) {
 933          return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
 934      }
 935  
 936      // Send a refreshed nonce in header.
 937      rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
 938  
 939      return true;
 940  }
 941  
 942  /**
 943   * Collects cookie authentication status.
 944   *
 945   * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
 946   *
 947   * @since 4.4.0
 948   *
 949   * @see current_action()
 950   * @global mixed $wp_rest_auth_cookie
 951   */
 952  function rest_cookie_collect_status() {
 953      global $wp_rest_auth_cookie;
 954  
 955      $status_type = current_action();
 956  
 957      if ( 'auth_cookie_valid' !== $status_type ) {
 958          $wp_rest_auth_cookie = substr( $status_type, 12 );
 959          return;
 960      }
 961  
 962      $wp_rest_auth_cookie = true;
 963  }
 964  
 965  /**
 966   * Parses an RFC3339 time into a Unix timestamp.
 967   *
 968   * @since 4.4.0
 969   *
 970   * @param string $date      RFC3339 timestamp.
 971   * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
 972   *                          the timestamp's timezone. Default false.
 973   * @return int Unix timestamp.
 974   */
 975  function rest_parse_date( $date, $force_utc = false ) {
 976      if ( $force_utc ) {
 977          $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
 978      }
 979  
 980      $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
 981  
 982      if ( ! preg_match( $regex, $date, $matches ) ) {
 983          return false;
 984      }
 985  
 986      return strtotime( $date );
 987  }
 988  
 989  /**
 990   * Parses a 3 or 6 digit hex color (with #).
 991   *
 992   * @since 5.4.0
 993   *
 994   * @param string $color 3 or 6 digit hex color (with #).
 995   * @return string|false
 996   */
 997  function rest_parse_hex_color( $color ) {
 998      $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|';
 999      if ( ! preg_match( $regex, $color, $matches ) ) {
1000          return false;
1001      }
1002  
1003      return $color;
1004  }
1005  
1006  /**
1007   * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
1008   *
1009   * @since 4.4.0
1010   *
1011   * @see rest_parse_date()
1012   *
1013   * @param string $date   RFC3339 timestamp.
1014   * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
1015   * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
1016   *                    null on failure.
1017   */
1018  function rest_get_date_with_gmt( $date, $is_utc = false ) {
1019      /*
1020       * Whether or not the original date actually has a timezone string
1021       * changes the way we need to do timezone conversion.
1022       * Store this info before parsing the date, and use it later.
1023       */
1024      $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
1025  
1026      $date = rest_parse_date( $date );
1027  
1028      if ( empty( $date ) ) {
1029          return null;
1030      }
1031  
1032      /*
1033       * At this point $date could either be a local date (if we were passed
1034       * a *local* date without a timezone offset) or a UTC date (otherwise).
1035       * Timezone conversion needs to be handled differently between these two cases.
1036       */
1037      if ( ! $is_utc && ! $has_timezone ) {
1038          $local = gmdate( 'Y-m-d H:i:s', $date );
1039          $utc   = get_gmt_from_date( $local );
1040      } else {
1041          $utc   = gmdate( 'Y-m-d H:i:s', $date );
1042          $local = get_date_from_gmt( $utc );
1043      }
1044  
1045      return array( $local, $utc );
1046  }
1047  
1048  /**
1049   * Returns a contextual HTTP error code for authorization failure.
1050   *
1051   * @since 4.7.0
1052   *
1053   * @return integer 401 if the user is not logged in, 403 if the user is logged in.
1054   */
1055  function rest_authorization_required_code() {
1056      return is_user_logged_in() ? 403 : 401;
1057  }
1058  
1059  /**
1060   * Validate a request argument based on details registered to the route.
1061   *
1062   * @since 4.7.0
1063   *
1064   * @param  mixed            $value
1065   * @param  WP_REST_Request  $request
1066   * @param  string           $param
1067   * @return true|WP_Error
1068   */
1069  function rest_validate_request_arg( $value, $request, $param ) {
1070      $attributes = $request->get_attributes();
1071      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1072          return true;
1073      }
1074      $args = $attributes['args'][ $param ];
1075  
1076      return rest_validate_value_from_schema( $value, $args, $param );
1077  }
1078  
1079  /**
1080   * Sanitize a request argument based on details registered to the route.
1081   *
1082   * @since 4.7.0
1083   *
1084   * @param  mixed            $value
1085   * @param  WP_REST_Request  $request
1086   * @param  string           $param
1087   * @return mixed
1088   */
1089  function rest_sanitize_request_arg( $value, $request, $param ) {
1090      $attributes = $request->get_attributes();
1091      if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1092          return $value;
1093      }
1094      $args = $attributes['args'][ $param ];
1095  
1096      return rest_sanitize_value_from_schema( $value, $args );
1097  }
1098  
1099  /**
1100   * Parse a request argument based on details registered to the route.
1101   *
1102   * Runs a validation check and sanitizes the value, primarily to be used via
1103   * the `sanitize_callback` arguments in the endpoint args registration.
1104   *
1105   * @since 4.7.0
1106   *
1107   * @param  mixed            $value
1108   * @param  WP_REST_Request  $request
1109   * @param  string           $param
1110   * @return mixed
1111   */
1112  function rest_parse_request_arg( $value, $request, $param ) {
1113      $is_valid = rest_validate_request_arg( $value, $request, $param );
1114  
1115      if ( is_wp_error( $is_valid ) ) {
1116          return $is_valid;
1117      }
1118  
1119      $value = rest_sanitize_request_arg( $value, $request, $param );
1120  
1121      return $value;
1122  }
1123  
1124  /**
1125   * Determines if an IP address is valid.
1126   *
1127   * Handles both IPv4 and IPv6 addresses.
1128   *
1129   * @since 4.7.0
1130   *
1131   * @param  string $ip IP address.
1132   * @return string|false The valid IP address, otherwise false.
1133   */
1134  function rest_is_ip_address( $ip ) {
1135      $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]?)$/';
1136  
1137      if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
1138          return false;
1139      }
1140  
1141      return $ip;
1142  }
1143  
1144  /**
1145   * Changes a boolean-like value into the proper boolean value.
1146   *
1147   * @since 4.7.0
1148   *
1149   * @param bool|string|int $value The value being evaluated.
1150   * @return boolean Returns the proper associated boolean value.
1151   */
1152  function rest_sanitize_boolean( $value ) {
1153      // String values are translated to `true`; make sure 'false' is false.
1154      if ( is_string( $value ) ) {
1155          $value = strtolower( $value );
1156          if ( in_array( $value, array( 'false', '0' ), true ) ) {
1157              $value = false;
1158          }
1159      }
1160  
1161      // Everything else will map nicely to boolean.
1162      return (bool) $value;
1163  }
1164  
1165  /**
1166   * Determines if a given value is boolean-like.
1167   *
1168   * @since 4.7.0
1169   *
1170   * @param bool|string $maybe_bool The value being evaluated.
1171   * @return boolean True if a boolean, otherwise false.
1172   */
1173  function rest_is_boolean( $maybe_bool ) {
1174      if ( is_bool( $maybe_bool ) ) {
1175          return true;
1176      }
1177  
1178      if ( is_string( $maybe_bool ) ) {
1179          $maybe_bool = strtolower( $maybe_bool );
1180  
1181          $valid_boolean_values = array(
1182              'false',
1183              'true',
1184              '0',
1185              '1',
1186          );
1187  
1188          return in_array( $maybe_bool, $valid_boolean_values, true );
1189      }
1190  
1191      if ( is_int( $maybe_bool ) ) {
1192          return in_array( $maybe_bool, array( 0, 1 ), true );
1193      }
1194  
1195      return false;
1196  }
1197  
1198  /**
1199   * Retrieves the avatar urls in various sizes.
1200   *
1201   * @since 4.7.0
1202   *
1203   * @see get_avatar_url()
1204   *
1205   * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
1206   *                           user email, WP_User object, WP_Post object, or WP_Comment object.
1207   * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
1208   */
1209  function rest_get_avatar_urls( $id_or_email ) {
1210      $avatar_sizes = rest_get_avatar_sizes();
1211  
1212      $urls = array();
1213      foreach ( $avatar_sizes as $size ) {
1214          $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
1215      }
1216  
1217      return $urls;
1218  }
1219  
1220  /**
1221   * Retrieves the pixel sizes for avatars.
1222   *
1223   * @since 4.7.0
1224   *
1225   * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
1226   */
1227  function rest_get_avatar_sizes() {
1228      /**
1229       * Filters the REST avatar sizes.
1230       *
1231       * Use this filter to adjust the array of sizes returned by the
1232       * `rest_get_avatar_sizes` function.
1233       *
1234       * @since 4.4.0
1235       *
1236       * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
1237       *                     Default `[ 24, 48, 96 ]`.
1238       */
1239      return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
1240  }
1241  
1242  /**
1243   * Validate a value based on a schema.
1244   *
1245   * @since 4.7.0
1246   * @since 4.9.0 Support the "object" type.
1247   * @since 5.2.0 Support validating "additionalProperties" against a schema.
1248   * @since 5.3.0 Support multiple types.
1249   * @since 5.4.0 Convert an empty string to an empty object.
1250   * @since 5.5.0 Add the "uuid" and "hex-color" formats.
1251   *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
1252   *              Validate required properties.
1253   *
1254   * @param mixed  $value The value to validate.
1255   * @param array  $args  Schema array to use for validation.
1256   * @param string $param The parameter name, used in error messages.
1257   * @return true|WP_Error
1258   */
1259  function rest_validate_value_from_schema( $value, $args, $param = '' ) {
1260      if ( is_array( $args['type'] ) ) {
1261          foreach ( $args['type'] as $type ) {
1262              $type_args         = $args;
1263              $type_args['type'] = $type;
1264  
1265              if ( true === rest_validate_value_from_schema( $value, $type_args, $param ) ) {
1266                  return true;
1267              }
1268          }
1269  
1270          /* translators: 1: Parameter, 2: List of types. */
1271          return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
1272      }
1273  
1274      if ( 'array' === $args['type'] ) {
1275          if ( ! is_null( $value ) ) {
1276              $value = wp_parse_list( $value );
1277          }
1278  
1279          if ( ! wp_is_numeric_array( $value ) ) {
1280              /* translators: 1: Parameter, 2: Type name. */
1281              return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
1282          }
1283  
1284          foreach ( $value as $index => $v ) {
1285              $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
1286              if ( is_wp_error( $is_valid ) ) {
1287                  return $is_valid;
1288              }
1289          }
1290      }
1291  
1292      if ( 'object' === $args['type'] ) {
1293          if ( '' === $value ) {
1294              $value = array();
1295          }
1296  
1297          if ( $value instanceof stdClass ) {
1298              $value = (array) $value;
1299          }
1300  
1301          if ( $value instanceof JsonSerializable ) {
1302              $value = $value->jsonSerialize();
1303          }
1304  
1305          if ( ! is_array( $value ) ) {
1306              /* translators: 1: Parameter, 2: Type name. */
1307              return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
1308          }
1309  
1310          if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
1311              foreach ( $args['required'] as $name ) {
1312                  if ( ! array_key_exists( $name, $value ) ) {
1313                      /* translators: 1: Property of an object, 2: Parameter. */
1314                      return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
1315                  }
1316              }
1317          } elseif ( isset( $args['properties'] ) ) { // schema version 3
1318              foreach ( $args['properties'] as $name => $property ) {
1319                  if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
1320                      /* translators: 1: Property of an object, 2: Parameter. */
1321                      return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
1322                  }
1323              }
1324          }
1325  
1326          foreach ( $value as $property => $v ) {
1327              if ( isset( $args['properties'][ $property ] ) ) {
1328                  $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
1329                  if ( is_wp_error( $is_valid ) ) {
1330                      return $is_valid;
1331                  }
1332              } elseif ( isset( $args['additionalProperties'] ) ) {
1333                  if ( false === $args['additionalProperties'] ) {
1334                      /* translators: %s: Property of an object. */
1335                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
1336                  }
1337  
1338                  if ( is_array( $args['additionalProperties'] ) ) {
1339                      $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
1340                      if ( is_wp_error( $is_valid ) ) {
1341                          return $is_valid;
1342                      }
1343                  }
1344              }
1345          }
1346      }
1347  
1348      if ( 'null' === $args['type'] ) {
1349          if ( null !== $value ) {
1350              /* translators: 1: Parameter, 2: Type name. */
1351              return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
1352          }
1353  
1354          return true;
1355      }
1356  
1357      if ( ! empty( $args['enum'] ) ) {
1358          if ( ! in_array( $value, $args['enum'], true ) ) {
1359              /* translators: 1: Parameter, 2: List of valid values. */
1360              return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
1361          }
1362      }
1363  
1364      if ( in_array( $args['type'], array( 'integer', 'number' ), true ) && ! is_numeric( $value ) ) {
1365          /* translators: 1: Parameter, 2: Type name. */
1366          return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
1367      }
1368  
1369      if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
1370          /* translators: 1: Parameter, 2: Type name. */
1371          return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
1372      }
1373  
1374      if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
1375          /* translators: 1: Parameter, 2: Type name. */
1376          return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
1377      }
1378  
1379      if ( 'string' === $args['type'] ) {
1380          if ( ! is_string( $value ) ) {
1381              /* translators: 1: Parameter, 2: Type name. */
1382              return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
1383          }
1384  
1385          if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
1386              return new WP_Error(
1387                  'rest_invalid_param',
1388                  sprintf(
1389                      /* translators: 1: Parameter, 2: Number of characters. */
1390                      _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ),
1391                      $param,
1392                      number_format_i18n( $args['minLength'] )
1393                  )
1394              );
1395          }
1396  
1397          if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
1398              return new WP_Error(
1399                  'rest_invalid_param',
1400                  sprintf(
1401                      /* translators: 1: Parameter, 2: Number of characters. */
1402                      _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ),
1403                      $param,
1404                      number_format_i18n( $args['maxLength'] )
1405                  )
1406              );
1407          }
1408  
1409          if ( isset( $args['pattern'] ) ) {
1410              $pattern = str_replace( '#', '\\#', $args['pattern'] );
1411              if ( ! preg_match( '#' . $pattern . '#u', $value ) ) {
1412                  /* translators: 1: Parameter, 2: Pattern. */
1413                  return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
1414              }
1415          }
1416      }
1417  
1418      if ( isset( $args['format'] ) ) {
1419          switch ( $args['format'] ) {
1420              case 'hex-color':
1421                  if ( ! rest_parse_hex_color( $value ) ) {
1422                      return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
1423                  }
1424                  break;
1425  
1426              case 'date-time':
1427                  if ( ! rest_parse_date( $value ) ) {
1428                      return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
1429                  }
1430                  break;
1431  
1432              case 'email':
1433                  if ( ! is_email( $value ) ) {
1434                      return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
1435                  }
1436                  break;
1437              case 'ip':
1438                  if ( ! rest_is_ip_address( $value ) ) {
1439                      /* translators: %s: IP address. */
1440                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
1441                  }
1442                  break;
1443              case 'uuid':
1444                  if ( ! wp_is_uuid( $value ) ) {
1445                      /* translators: %s is the name of a JSON field expecting a valid uuid. */
1446                      return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
1447                  }
1448                  break;
1449          }
1450      }
1451  
1452      if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
1453          if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
1454              if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
1455                  /* translators: 1: Parameter, 2: Minimum number. */
1456                  return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
1457              } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
1458                  /* translators: 1: Parameter, 2: Minimum number. */
1459                  return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
1460              }
1461          } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
1462              if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
1463                  /* translators: 1: Parameter, 2: Maximum number. */
1464                  return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
1465              } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
1466                  /* translators: 1: Parameter, 2: Maximum number. */
1467                  return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
1468              }
1469          } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
1470              if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
1471                  if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
1472                      /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
1473                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
1474                  }
1475              } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
1476                  if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
1477                      /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
1478                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
1479                  }
1480              } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
1481                  if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
1482                      /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
1483                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
1484                  }
1485              } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
1486                  if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
1487                      /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
1488                      return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
1489                  }
1490              }
1491          }
1492      }
1493  
1494      return true;
1495  }
1496  
1497  /**
1498   * Sanitize a value based on a schema.
1499   *
1500   * @since 4.7.0
1501   *
1502   * @param mixed $value The value to sanitize.
1503   * @param array $args  Schema array to use for sanitization.
1504   * @return true|WP_Error
1505   */
1506  function rest_sanitize_value_from_schema( $value, $args ) {
1507      if ( is_array( $args['type'] ) ) {
1508          // Determine which type the value was validated against,
1509          // and use that type when performing sanitization.
1510          $validated_type = '';
1511  
1512          foreach ( $args['type'] as $type ) {
1513              $type_args         = $args;
1514              $type_args['type'] = $type;
1515  
1516              if ( ! is_wp_error( rest_validate_value_from_schema( $value, $type_args ) ) ) {
1517                  $validated_type = $type;
1518                  break;
1519              }
1520          }
1521  
1522          if ( ! $validated_type ) {
1523              return null;
1524          }
1525  
1526          $args['type'] = $validated_type;
1527      }
1528  
1529      if ( 'array' === $args['type'] ) {
1530          if ( empty( $args['items'] ) ) {
1531              return (array) $value;
1532          }
1533  
1534          $value = wp_parse_list( $value );
1535          foreach ( $value as $index => $v ) {
1536              $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
1537          }
1538  
1539          // Normalize to numeric array so nothing unexpected is in the keys.
1540          $value = array_values( $value );
1541          return $value;
1542      }
1543  
1544      if ( 'object' === $args['type'] ) {
1545          if ( $value instanceof stdClass ) {
1546              $value = (array) $value;
1547          }
1548  
1549          if ( $value instanceof JsonSerializable ) {
1550              $value = $value->jsonSerialize();
1551          }
1552  
1553          if ( ! is_array( $value ) ) {
1554              return array();
1555          }
1556  
1557          foreach ( $value as $property => $v ) {
1558              if ( isset( $args['properties'][ $property ] ) ) {
1559                  $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
1560              } elseif ( isset( $args['additionalProperties'] ) ) {
1561                  if ( false === $args['additionalProperties'] ) {
1562                      unset( $value[ $property ] );
1563                  } elseif ( is_array( $args['additionalProperties'] ) ) {
1564                      $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'] );
1565                  }
1566              }
1567          }
1568  
1569          return $value;
1570      }
1571  
1572      if ( 'null' === $args['type'] ) {
1573          return null;
1574      }
1575  
1576      if ( 'integer' === $args['type'] ) {
1577          return (int) $value;
1578      }
1579  
1580      if ( 'number' === $args['type'] ) {
1581          return (float) $value;
1582      }
1583  
1584      if ( 'boolean' === $args['type'] ) {
1585          return rest_sanitize_boolean( $value );
1586      }
1587  
1588      if ( isset( $args['format'] ) ) {
1589          switch ( $args['format'] ) {
1590              case 'hex-color':
1591                  return (string) sanitize_hex_color( $value );
1592  
1593              case 'date-time':
1594                  return sanitize_text_field( $value );
1595  
1596              case 'email':
1597                  // sanitize_email() validates, which would be unexpected.
1598                  return sanitize_text_field( $value );
1599  
1600              case 'uri':
1601                  return esc_url_raw( $value );
1602  
1603              case 'ip':
1604                  return sanitize_text_field( $value );
1605  
1606              case 'uuid':
1607                  return sanitize_text_field( $value );
1608          }
1609      }
1610  
1611      if ( 'string' === $args['type'] ) {
1612          return strval( $value );
1613      }
1614  
1615      return $value;
1616  }
1617  
1618  /**
1619   * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
1620   * Expected to be called in the context of `array_reduce`.
1621   *
1622   * @since 5.0.0
1623   *
1624   * @param  array  $memo Reduce accumulator.
1625   * @param  string $path REST API path to preload.
1626   * @return array        Modified reduce accumulator.
1627   */
1628  function rest_preload_api_request( $memo, $path ) {
1629      // array_reduce() doesn't support passing an array in PHP 5.2,
1630      // so we need to make sure we start with one.
1631      if ( ! is_array( $memo ) ) {
1632          $memo = array();
1633      }
1634  
1635      if ( empty( $path ) ) {
1636          return $memo;
1637      }
1638  
1639      $method = 'GET';
1640      if ( is_array( $path ) && 2 === count( $path ) ) {
1641          $method = end( $path );
1642          $path   = reset( $path );
1643  
1644          if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
1645              $method = 'GET';
1646          }
1647      }
1648  
1649      $path_parts = parse_url( $path );
1650      if ( false === $path_parts ) {
1651          return $memo;
1652      }
1653  
1654      $request = new WP_REST_Request( $method, $path_parts['path'] );
1655      if ( ! empty( $path_parts['query'] ) ) {
1656          parse_str( $path_parts['query'], $query_params );
1657          $request->set_query_params( $query_params );
1658      }
1659  
1660      $response = rest_do_request( $request );
1661      if ( 200 === $response->status ) {
1662          $server = rest_get_server();
1663          $data   = (array) $response->get_data();
1664          $links  = $server::get_compact_response_links( $response );
1665          if ( ! empty( $links ) ) {
1666              $data['_links'] = $links;
1667          }
1668  
1669          if ( 'OPTIONS' === $method ) {
1670              $response = rest_send_allow_header( $response, $server, $request );
1671  
1672              $memo[ $method ][ $path ] = array(
1673                  'body'    => $data,
1674                  'headers' => $response->headers,
1675              );
1676          } else {
1677              $memo[ $path ] = array(
1678                  'body'    => $data,
1679                  'headers' => $response->headers,
1680              );
1681          }
1682      }
1683  
1684      return $memo;
1685  }
1686  
1687  /**
1688   * Parses the "_embed" parameter into the list of resources to embed.
1689   *
1690   * @since 5.4.0
1691   *
1692   * @param string|array $embed Raw "_embed" parameter value.
1693   * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
1694   */
1695  function rest_parse_embed_param( $embed ) {
1696      if ( ! $embed || 'true' === $embed || '1' === $embed ) {
1697          return true;
1698      }
1699  
1700      $rels = wp_parse_list( $embed );
1701  
1702      if ( ! $rels ) {
1703          return true;
1704      }
1705  
1706      return $rels;
1707  }
1708  
1709  /**
1710   * Filters the response to remove any fields not available in the given context.
1711   *
1712   * @since 5.5.0
1713   *
1714   * @param array|object $data    The response data to modify.
1715   * @param array        $schema  The schema for the endpoint used to filter the response.
1716   * @param string       $context The requested context.
1717   * @return array|object The filtered response data.
1718   */
1719  function rest_filter_response_by_context( $data, $schema, $context ) {
1720      if ( ! is_array( $data ) && ! is_object( $data ) ) {
1721          return $data;
1722      }
1723  
1724      if ( isset( $schema['type'] ) ) {
1725          $type = $schema['type'];
1726      } elseif ( isset( $schema['properties'] ) ) {
1727          $type = 'object'; // Back compat if a developer accidentally omitted the type.
1728      } else {
1729          return $data;
1730      }
1731  
1732      foreach ( $data as $key => $value ) {
1733          $check = array();
1734  
1735          if ( 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) ) ) {
1736              $check = isset( $schema['items'] ) ? $schema['items'] : array();
1737          } elseif ( 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) ) ) {
1738              if ( isset( $schema['properties'][ $key ] ) ) {
1739                  $check = $schema['properties'][ $key ];
1740              } elseif ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) {
1741                  $check = $schema['additionalProperties'];
1742              }
1743          }
1744  
1745          if ( ! isset( $check['context'] ) ) {
1746              continue;
1747          }
1748  
1749          if ( ! in_array( $context, $check['context'], true ) ) {
1750              if ( is_object( $data ) ) {
1751                  unset( $data->$key );
1752              } else {
1753                  unset( $data[ $key ] );
1754              }
1755          } elseif ( is_array( $value ) || is_object( $value ) ) {
1756              $new_value = rest_filter_response_by_context( $value, $check, $context );
1757  
1758              if ( is_object( $data ) ) {
1759                  $data->$key = $new_value;
1760              } else {
1761                  $data[ $key ] = $new_value;
1762              }
1763          }
1764      }
1765  
1766      return $data;
1767  }


Generated: Sun May 31 01:00:03 2020 Cross-referenced by PHPXref 0.7.1