home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api.php < prev    next >
Encoding:
PHP Script  |  2017-10-24  |  37.6 KB  |  1,296 lines

  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.  * @since 4.4.0
  21.  *
  22.  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
  23.  * @param string $route     The base URL for route you are adding.
  24.  * @param array  $args      Optional. Either an array of options for the endpoint, or an array of arrays for
  25.  *                          multiple methods. Default empty array.
  26.  * @param bool   $override  Optional. If the route already exists, should we override it? True overrides,
  27.  *                          false merges (with newer overriding if duplicate keys exist). Default false.
  28.  * @return bool True on success, false on error.
  29.  */
  30. function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
  31.     if ( empty( $namespace ) ) {
  32.         /*
  33.          * Non-namespaced routes are not allowed, with the exception of the main
  34.          * and namespace indexes. If you really need to register a
  35.          * non-namespaced route, call `WP_REST_Server::register_route` directly.
  36.          */
  37.         _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
  38.         return false;
  39.     } else if ( empty( $route ) ) {
  40.         _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
  41.         return false;
  42.     }
  43.  
  44.     if ( isset( $args['args'] ) ) {
  45.         $common_args = $args['args'];
  46.         unset( $args['args'] );
  47.     } else {
  48.         $common_args = array();
  49.     }
  50.  
  51.     if ( isset( $args['callback'] ) ) {
  52.         // Upgrade a single set to multiple.
  53.         $args = array( $args );
  54.     }
  55.  
  56.     $defaults = array(
  57.         'methods'         => 'GET',
  58.         'callback'        => null,
  59.         'args'            => array(),
  60.     );
  61.     foreach ( $args as $key => &$arg_group ) {
  62.         if ( ! is_numeric( $key ) ) {
  63.             // Route option, skip here.
  64.             continue;
  65.         }
  66.  
  67.         $arg_group = array_merge( $defaults, $arg_group );
  68.         $arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
  69.     }
  70.  
  71.     $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
  72.     rest_get_server()->register_route( $namespace, $full_route, $args, $override );
  73.     return true;
  74. }
  75.  
  76. /**
  77.  * Registers a new field on an existing WordPress object type.
  78.  *
  79.  * @since 4.7.0
  80.  *
  81.  * @global array $wp_rest_additional_fields Holds registered fields, organized
  82.  *                                          by object type.
  83.  *
  84.  * @param string|array $object_type Object(s) the field is being registered
  85.  *                                  to, "post"|"term"|"comment" etc.
  86.  * @param string $attribute         The attribute name.
  87.  * @param array  $args {
  88.  *     Optional. An array of arguments used to handle the registered field.
  89.  *
  90.  *     @type string|array|null $get_callback    Optional. The callback function used to retrieve the field
  91.  *                                              value. Default is 'null', the field will not be returned in
  92.  *                                              the response.
  93.  *     @type string|array|null $update_callback Optional. The callback function used to set and update the
  94.  *                                              field value. Default is 'null', the value cannot be set or
  95.  *                                              updated.
  96.  *     @type string|array|null $schema          Optional. The callback function used to create the schema for
  97.  *                                              this field. Default is 'null', no schema entry will be returned.
  98.  * }
  99.  */
  100. function register_rest_field( $object_type, $attribute, $args = array() ) {
  101.     $defaults = array(
  102.         'get_callback'    => null,
  103.         'update_callback' => null,
  104.         'schema'          => null,
  105.     );
  106.  
  107.     $args = wp_parse_args( $args, $defaults );
  108.  
  109.     global $wp_rest_additional_fields;
  110.  
  111.     $object_types = (array) $object_type;
  112.  
  113.     foreach ( $object_types as $object_type ) {
  114.         $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
  115.     }
  116. }
  117.  
  118. /**
  119.  * Registers rewrite rules for the API.
  120.  *
  121.  * @since 4.4.0
  122.  *
  123.  * @see rest_api_register_rewrites()
  124.  * @global WP $wp Current WordPress environment instance.
  125.  */
  126. function rest_api_init() {
  127.     rest_api_register_rewrites();
  128.  
  129.     global $wp;
  130.     $wp->add_query_var( 'rest_route' );
  131. }
  132.  
  133. /**
  134.  * Adds REST rewrite rules.
  135.  *
  136.  * @since 4.4.0
  137.  *
  138.  * @see add_rewrite_rule()
  139.  * @global WP_Rewrite $wp_rewrite
  140.  */
  141. function rest_api_register_rewrites() {
  142.     global $wp_rewrite;
  143.  
  144.     add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
  145.     add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
  146.     add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
  147.     add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
  148. }
  149.  
  150. /**
  151.  * Registers the default REST API filters.
  152.  *
  153.  * Attached to the {@see 'rest_api_init'} action
  154.  * to make testing and disabling these filters easier.
  155.  *
  156.  * @since 4.4.0
  157.  */
  158. function rest_api_default_filters() {
  159.     // Deprecated reporting.
  160.     add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
  161.     add_filter( 'deprecated_function_trigger_error', '__return_false' );
  162.     add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
  163.     add_filter( 'deprecated_argument_trigger_error', '__return_false' );
  164.  
  165.     // Default serving.
  166.     add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
  167.     add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
  168.     add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
  169.  
  170.     add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
  171. }
  172.  
  173. /**
  174.  * Registers default REST API routes.
  175.  *
  176.  * @since 4.7.0
  177.  */
  178. function create_initial_rest_routes() {
  179.     foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
  180.         $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
  181.  
  182.         if ( ! class_exists( $class ) ) {
  183.             continue;
  184.         }
  185.         $controller = new $class( $post_type->name );
  186.         if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
  187.             continue;
  188.         }
  189.  
  190.         $controller->register_routes();
  191.  
  192.         if ( post_type_supports( $post_type->name, 'revisions' ) ) {
  193.             $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
  194.             $revisions_controller->register_routes();
  195.         }
  196.     }
  197.  
  198.     // Post types.
  199.     $controller = new WP_REST_Post_Types_Controller;
  200.     $controller->register_routes();
  201.  
  202.     // Post statuses.
  203.     $controller = new WP_REST_Post_Statuses_Controller;
  204.     $controller->register_routes();
  205.  
  206.     // Taxonomies.
  207.     $controller = new WP_REST_Taxonomies_Controller;
  208.     $controller->register_routes();
  209.  
  210.     // Terms.
  211.     foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
  212.         $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
  213.  
  214.         if ( ! class_exists( $class ) ) {
  215.             continue;
  216.         }
  217.         $controller = new $class( $taxonomy->name );
  218.         if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
  219.             continue;
  220.         }
  221.  
  222.         $controller->register_routes();
  223.     }
  224.  
  225.     // Users.
  226.     $controller = new WP_REST_Users_Controller;
  227.     $controller->register_routes();
  228.  
  229.     // Comments.
  230.     $controller = new WP_REST_Comments_Controller;
  231.     $controller->register_routes();
  232.  
  233.     // Settings.
  234.     $controller = new WP_REST_Settings_Controller;
  235.     $controller->register_routes();
  236. }
  237.  
  238. /**
  239.  * Loads the REST API.
  240.  *
  241.  * @since 4.4.0
  242.  *
  243.  * @global WP             $wp             Current WordPress environment instance.
  244.  */
  245. function rest_api_loaded() {
  246.     if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
  247.         return;
  248.     }
  249.  
  250.     /**
  251.      * Whether this is a REST Request.
  252.      *
  253.      * @since 4.4.0
  254.      * @var bool
  255.      */
  256.     define( 'REST_REQUEST', true );
  257.  
  258.     // Initialize the server.
  259.     $server = rest_get_server();
  260.  
  261.     // Fire off the request.
  262.     $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
  263.     if ( empty( $route ) ) {
  264.         $route = '/';
  265.     }
  266.     $server->serve_request( $route );
  267.  
  268.     // We're done.
  269.     die();
  270. }
  271.  
  272. /**
  273.  * Retrieves the URL prefix for any API resource.
  274.  *
  275.  * @since 4.4.0
  276.  *
  277.  * @return string Prefix.
  278.  */
  279. function rest_get_url_prefix() {
  280.     /**
  281.      * Filters the REST URL prefix.
  282.      *
  283.      * @since 4.4.0
  284.      *
  285.      * @param string $prefix URL prefix. Default 'wp-json'.
  286.      */
  287.     return apply_filters( 'rest_url_prefix', 'wp-json' );
  288. }
  289.  
  290. /**
  291.  * Retrieves the URL to a REST endpoint on a site.
  292.  *
  293.  * Note: The returned URL is NOT escaped.
  294.  *
  295.  * @since 4.4.0
  296.  *
  297.  * @todo Check if this is even necessary
  298.  * @global WP_Rewrite $wp_rewrite
  299.  *
  300.  * @param int    $blog_id Optional. Blog ID. Default of null returns URL for current blog.
  301.  * @param string $path    Optional. REST route. Default '/'.
  302.  * @param string $scheme  Optional. Sanitization scheme. Default 'rest'.
  303.  * @return string Full URL to the endpoint.
  304.  */
  305. function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
  306.     if ( empty( $path ) ) {
  307.         $path = '/';
  308.     }
  309.  
  310.     if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
  311.         global $wp_rewrite;
  312.  
  313.         if ( $wp_rewrite->using_index_permalinks() ) {
  314.             $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
  315.         } else {
  316.             $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
  317.         }
  318.  
  319.         $url .= '/' . ltrim( $path, '/' );
  320.     } else {
  321.         $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
  322.         // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php
  323.         // To work around this, we manually add index.php to the URL, avoiding the redirect.
  324.         if ( 'index.php' !== substr( $url, 9 ) ) {
  325.             $url .= 'index.php';
  326.         }
  327.  
  328.         $path = '/' . ltrim( $path, '/' );
  329.  
  330.         $url = add_query_arg( 'rest_route', $path, $url );
  331.     }
  332.  
  333.     if ( is_ssl() ) {
  334.         // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
  335.         if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
  336.             $url = set_url_scheme( $url, 'https' );
  337.         }
  338.     }
  339.  
  340.     if ( is_admin() && force_ssl_admin() ) {
  341.         // In this situation the home URL may be http:, and `is_ssl()` may be
  342.         // false, but the admin is served over https: (one way or another), so
  343.         // REST API usage will be blocked by browsers unless it is also served
  344.         // over HTTPS.
  345.         $url = set_url_scheme( $url, 'https' );
  346.     }
  347.  
  348.     /**
  349.      * Filters the REST URL.
  350.      *
  351.      * Use this filter to adjust the url returned by the get_rest_url() function.
  352.      *
  353.      * @since 4.4.0
  354.      *
  355.      * @param string $url     REST URL.
  356.      * @param string $path    REST route.
  357.      * @param int    $blog_id Blog ID.
  358.      * @param string $scheme  Sanitization scheme.
  359.      */
  360.     return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
  361. }
  362.  
  363. /**
  364.  * Retrieves the URL to a REST endpoint.
  365.  *
  366.  * Note: The returned URL is NOT escaped.
  367.  *
  368.  * @since 4.4.0
  369.  *
  370.  * @param string $path   Optional. REST route. Default empty.
  371.  * @param string $scheme Optional. Sanitization scheme. Default 'json'.
  372.  * @return string Full URL to the endpoint.
  373.  */
  374. function rest_url( $path = '', $scheme = 'json' ) {
  375.     return get_rest_url( null, $path, $scheme );
  376. }
  377.  
  378. /**
  379.  * Do a REST request.
  380.  *
  381.  * Used primarily to route internal requests through WP_REST_Server.
  382.  *
  383.  * @since 4.4.0
  384.  *
  385.  * @param WP_REST_Request|string $request Request.
  386.  * @return WP_REST_Response REST response.
  387.  */
  388. function rest_do_request( $request ) {
  389.     $request = rest_ensure_request( $request );
  390.     return rest_get_server()->dispatch( $request );
  391. }
  392.  
  393. /**
  394.  * Retrieves the current REST server instance.
  395.  *
  396.  * Instantiates a new instance if none exists already.
  397.  *
  398.  * @since 4.5.0
  399.  *
  400.  * @global WP_REST_Server $wp_rest_server REST server instance.
  401.  *
  402.  * @return WP_REST_Server REST server instance.
  403.  */
  404. function rest_get_server() {
  405.     /* @var WP_REST_Server $wp_rest_server */
  406.     global $wp_rest_server;
  407.  
  408.     if ( empty( $wp_rest_server ) ) {
  409.         /**
  410.          * Filters the REST Server Class.
  411.          *
  412.          * This filter allows you to adjust the server class used by the API, using a
  413.          * different class to handle requests.
  414.          *
  415.          * @since 4.4.0
  416.          *
  417.          * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
  418.          */
  419.         $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
  420.         $wp_rest_server = new $wp_rest_server_class;
  421.  
  422.         /**
  423.          * Fires when preparing to serve an API request.
  424.          *
  425.          * Endpoint objects should be created and register their hooks on this action rather
  426.          * than another action to ensure they're only loaded when needed.
  427.          *
  428.          * @since 4.4.0
  429.          *
  430.          * @param WP_REST_Server $wp_rest_server Server object.
  431.          */
  432.         do_action( 'rest_api_init', $wp_rest_server );
  433.     }
  434.  
  435.     return $wp_rest_server;
  436. }
  437.  
  438. /**
  439.  * Ensures request arguments are a request object (for consistency).
  440.  *
  441.  * @since 4.4.0
  442.  *
  443.  * @param array|WP_REST_Request $request Request to check.
  444.  * @return WP_REST_Request REST request instance.
  445.  */
  446. function rest_ensure_request( $request ) {
  447.     if ( $request instanceof WP_REST_Request ) {
  448.         return $request;
  449.     }
  450.  
  451.     return new WP_REST_Request( 'GET', '', $request );
  452. }
  453.  
  454. /**
  455.  * Ensures a REST response is a response object (for consistency).
  456.  *
  457.  * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
  458.  * without needing to double-check the object. Will also allow WP_Error to indicate error
  459.  * responses, so users should immediately check for this value.
  460.  *
  461.  * @since 4.4.0
  462.  *
  463.  * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
  464.  * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response
  465.  *                                is already an instance, WP_HTTP_Response, otherwise
  466.  *                                returns a new WP_REST_Response instance.
  467.  */
  468. function rest_ensure_response( $response ) {
  469.     if ( is_wp_error( $response ) ) {
  470.         return $response;
  471.     }
  472.  
  473.     if ( $response instanceof WP_HTTP_Response ) {
  474.         return $response;
  475.     }
  476.  
  477.     return new WP_REST_Response( $response );
  478. }
  479.  
  480. /**
  481.  * Handles _deprecated_function() errors.
  482.  *
  483.  * @since 4.4.0
  484.  *
  485.  * @param string $function    The function that was called.
  486.  * @param string $replacement The function that should have been called.
  487.  * @param string $version     Version.
  488.  */
  489. function rest_handle_deprecated_function( $function, $replacement, $version ) {
  490.     if ( ! WP_DEBUG || headers_sent() ) {
  491.         return;
  492.     }
  493.     if ( ! empty( $replacement ) ) {
  494.         /* translators: 1: function name, 2: WordPress version number, 3: new function name */
  495.         $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
  496.     } else {
  497.         /* translators: 1: function name, 2: WordPress version number */
  498.         $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
  499.     }
  500.  
  501.     header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
  502. }
  503.  
  504. /**
  505.  * Handles _deprecated_argument() errors.
  506.  *
  507.  * @since 4.4.0
  508.  *
  509.  * @param string $function    The function that was called.
  510.  * @param string $message     A message regarding the change.
  511.  * @param string $version     Version.
  512.  */
  513. function rest_handle_deprecated_argument( $function, $message, $version ) {
  514.     if ( ! WP_DEBUG || headers_sent() ) {
  515.         return;
  516.     }
  517.     if ( ! empty( $message ) ) {
  518.         /* translators: 1: function name, 2: WordPress version number, 3: error message */
  519.         $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
  520.     } else {
  521.         /* translators: 1: function name, 2: WordPress version number */
  522.         $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
  523.     }
  524.  
  525.     header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
  526. }
  527.  
  528. /**
  529.  * Sends Cross-Origin Resource Sharing headers with API requests.
  530.  *
  531.  * @since 4.4.0
  532.  *
  533.  * @param mixed $value Response data.
  534.  * @return mixed Response data.
  535.  */
  536. function rest_send_cors_headers( $value ) {
  537.     $origin = get_http_origin();
  538.  
  539.     if ( $origin ) {
  540.         // Requests from file:// and data: URLs send "Origin: null"
  541.         if ( 'null' !== $origin ) {
  542.             $origin = esc_url_raw( $origin );
  543.         }
  544.         header( 'Access-Control-Allow-Origin: ' . $origin );
  545.         header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
  546.         header( 'Access-Control-Allow-Credentials: true' );
  547.         header( 'Vary: Origin' );
  548.     }
  549.  
  550.     return $value;
  551. }
  552.  
  553. /**
  554.  * Handles OPTIONS requests for the server.
  555.  *
  556.  * This is handled outside of the server code, as it doesn't obey normal route
  557.  * mapping.
  558.  *
  559.  * @since 4.4.0
  560.  *
  561.  * @param mixed           $response Current response, either response or `null` to indicate pass-through.
  562.  * @param WP_REST_Server  $handler  ResponseHandler instance (usually WP_REST_Server).
  563.  * @param WP_REST_Request $request  The request that was used to make current response.
  564.  * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
  565.  */
  566. function rest_handle_options_request( $response, $handler, $request ) {
  567.     if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
  568.         return $response;
  569.     }
  570.  
  571.     $response = new WP_REST_Response();
  572.     $data = array();
  573.  
  574.     foreach ( $handler->get_routes() as $route => $endpoints ) {
  575.         $match = preg_match( '@^' . $route . '$@i', $request->get_route() );
  576.  
  577.         if ( ! $match ) {
  578.             continue;
  579.         }
  580.  
  581.         $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
  582.         $response->set_matched_route( $route );
  583.         break;
  584.     }
  585.  
  586.     $response->set_data( $data );
  587.     return $response;
  588. }
  589.  
  590. /**
  591.  * Sends the "Allow" header to state all methods that can be sent to the current route.
  592.  *
  593.  * @since 4.4.0
  594.  *
  595.  * @param WP_REST_Response $response Current response being served.
  596.  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
  597.  * @param WP_REST_Request  $request  The request that was used to make current response.
  598.  * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
  599.  */
  600. function rest_send_allow_header( $response, $server, $request ) {
  601.     $matched_route = $response->get_matched_route();
  602.  
  603.     if ( ! $matched_route ) {
  604.         return $response;
  605.     }
  606.  
  607.     $routes = $server->get_routes();
  608.  
  609.     $allowed_methods = array();
  610.  
  611.     // Get the allowed methods across the routes.
  612.     foreach ( $routes[ $matched_route ] as $_handler ) {
  613.         foreach ( $_handler['methods'] as $handler_method => $value ) {
  614.  
  615.             if ( ! empty( $_handler['permission_callback'] ) ) {
  616.  
  617.                 $permission = call_user_func( $_handler['permission_callback'], $request );
  618.  
  619.                 $allowed_methods[ $handler_method ] = true === $permission;
  620.             } else {
  621.                 $allowed_methods[ $handler_method ] = true;
  622.             }
  623.         }
  624.     }
  625.  
  626.     // Strip out all the methods that are not allowed (false values).
  627.     $allowed_methods = array_filter( $allowed_methods );
  628.  
  629.     if ( $allowed_methods ) {
  630.         $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
  631.     }
  632.  
  633.     return $response;
  634. }
  635.  
  636. /**
  637.  * Filter the API response to include only a white-listed set of response object fields.
  638.  *
  639.  * @since 4.8.0
  640.  *
  641.  * @param WP_REST_Response $response Current response being served.
  642.  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
  643.  * @param WP_REST_Request  $request  The request that was used to make current response.
  644.  *
  645.  * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
  646.  */
  647. function rest_filter_response_fields( $response, $server, $request ) {
  648.     if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
  649.         return $response;
  650.     }
  651.  
  652.     $data = $response->get_data();
  653.  
  654.     $fields = is_array( $request['_fields']  ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] );
  655.  
  656.     if ( 0 === count( $fields ) ) {
  657.         return $response;
  658.     }
  659.  
  660.     // Trim off outside whitespace from the comma delimited list.
  661.     $fields = array_map( 'trim', $fields );
  662.  
  663.     $fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) );
  664.  
  665.     if ( wp_is_numeric_array( $data ) ) {
  666.         $new_data = array();
  667.         foreach ( $data as $item ) {
  668.             $new_data[] = array_intersect_key( $item, $fields_as_keyed );
  669.         }
  670.     } else {
  671.         $new_data = array_intersect_key( $data, $fields_as_keyed );
  672.     }
  673.  
  674.     $response->set_data( $new_data );
  675.  
  676.     return $response;
  677. }
  678.  
  679. /**
  680.  * Adds the REST API URL to the WP RSD endpoint.
  681.  *
  682.  * @since 4.4.0
  683.  *
  684.  * @see get_rest_url()
  685.  */
  686. function rest_output_rsd() {
  687.     $api_root = get_rest_url();
  688.  
  689.     if ( empty( $api_root ) ) {
  690.         return;
  691.     }
  692.     ?>
  693.     <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
  694.     <?php
  695. }
  696.  
  697. /**
  698.  * Outputs the REST API link tag into page header.
  699.  *
  700.  * @since 4.4.0
  701.  *
  702.  * @see get_rest_url()
  703.  */
  704. function rest_output_link_wp_head() {
  705.     $api_root = get_rest_url();
  706.  
  707.     if ( empty( $api_root ) ) {
  708.         return;
  709.     }
  710.  
  711.     echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
  712. }
  713.  
  714. /**
  715.  * Sends a Link header for the REST API.
  716.  *
  717.  * @since 4.4.0
  718.  */
  719. function rest_output_link_header() {
  720.     if ( headers_sent() ) {
  721.         return;
  722.     }
  723.  
  724.     $api_root = get_rest_url();
  725.  
  726.     if ( empty( $api_root ) ) {
  727.         return;
  728.     }
  729.  
  730.     header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
  731. }
  732.  
  733. /**
  734.  * Checks for errors when using cookie-based authentication.
  735.  *
  736.  * WordPress' built-in cookie authentication is always active
  737.  * for logged in users. However, the API has to check nonces
  738.  * for each request to ensure users are not vulnerable to CSRF.
  739.  *
  740.  * @since 4.4.0
  741.  *
  742.  * @global mixed          $wp_rest_auth_cookie
  743.  *
  744.  * @param WP_Error|mixed $result Error from another authentication handler,
  745.  *                               null if we should handle it, or another value
  746.  *                               if not.
  747.  * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
  748.  */
  749. function rest_cookie_check_errors( $result ) {
  750.     if ( ! empty( $result ) ) {
  751.         return $result;
  752.     }
  753.  
  754.     global $wp_rest_auth_cookie;
  755.  
  756.     /*
  757.      * Is cookie authentication being used? (If we get an auth
  758.      * error, but we're still logged in, another authentication
  759.      * must have been used).
  760.      */
  761.     if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
  762.         return $result;
  763.     }
  764.  
  765.     // Determine if there is a nonce.
  766.     $nonce = null;
  767.  
  768.     if ( isset( $_REQUEST['_wpnonce'] ) ) {
  769.         $nonce = $_REQUEST['_wpnonce'];
  770.     } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
  771.         $nonce = $_SERVER['HTTP_X_WP_NONCE'];
  772.     }
  773.  
  774.     if ( null === $nonce ) {
  775.         // No nonce at all, so act as if it's an unauthenticated request.
  776.         wp_set_current_user( 0 );
  777.         return true;
  778.     }
  779.  
  780.     // Check the nonce.
  781.     $result = wp_verify_nonce( $nonce, 'wp_rest' );
  782.  
  783.     if ( ! $result ) {
  784.         return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
  785.     }
  786.  
  787.     // Send a refreshed nonce in header.
  788.     rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
  789.  
  790.     return true;
  791. }
  792.  
  793. /**
  794.  * Collects cookie authentication status.
  795.  *
  796.  * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
  797.  *
  798.  * @since 4.4.0
  799.  *
  800.  * @see current_action()
  801.  * @global mixed $wp_rest_auth_cookie
  802.  */
  803. function rest_cookie_collect_status() {
  804.     global $wp_rest_auth_cookie;
  805.  
  806.     $status_type = current_action();
  807.  
  808.     if ( 'auth_cookie_valid' !== $status_type ) {
  809.         $wp_rest_auth_cookie = substr( $status_type, 12 );
  810.         return;
  811.     }
  812.  
  813.     $wp_rest_auth_cookie = true;
  814. }
  815.  
  816. /**
  817.  * Parses an RFC3339 time into a Unix timestamp.
  818.  *
  819.  * @since 4.4.0
  820.  *
  821.  * @param string $date      RFC3339 timestamp.
  822.  * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
  823.  *                          the timestamp's timezone. Default false.
  824.  * @return int Unix timestamp.
  825.  */
  826. function rest_parse_date( $date, $force_utc = false ) {
  827.     if ( $force_utc ) {
  828.         $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
  829.     }
  830.  
  831.     $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
  832.  
  833.     if ( ! preg_match( $regex, $date, $matches ) ) {
  834.         return false;
  835.     }
  836.  
  837.     return strtotime( $date );
  838. }
  839.  
  840. /**
  841.  * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
  842.  *
  843.  * @since 4.4.0
  844.  *
  845.  * @see rest_parse_date()
  846.  *
  847.  * @param string $date   RFC3339 timestamp.
  848.  * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
  849.  * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
  850.  *                    null on failure.
  851.  */
  852. function rest_get_date_with_gmt( $date, $is_utc = false ) {
  853.     // Whether or not the original date actually has a timezone string
  854.     // changes the way we need to do timezone conversion.  Store this info
  855.     // before parsing the date, and use it later.
  856.     $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
  857.  
  858.     $date = rest_parse_date( $date );
  859.  
  860.     if ( empty( $date ) ) {
  861.         return null;
  862.     }
  863.  
  864.     // At this point $date could either be a local date (if we were passed a
  865.     // *local* date without a timezone offset) or a UTC date (otherwise).
  866.     // Timezone conversion needs to be handled differently between these two
  867.     // cases.
  868.     if ( ! $is_utc && ! $has_timezone ) {
  869.         $local = date( 'Y-m-d H:i:s', $date );
  870.         $utc = get_gmt_from_date( $local );
  871.     } else {
  872.         $utc = date( 'Y-m-d H:i:s', $date );
  873.         $local = get_date_from_gmt( $utc );
  874.     }
  875.  
  876.     return array( $local, $utc );
  877. }
  878.  
  879. /**
  880.  * Returns a contextual HTTP error code for authorization failure.
  881.  *
  882.  * @since 4.7.0
  883.  *
  884.  * @return integer 401 if the user is not logged in, 403 if the user is logged in.
  885.  */
  886. function rest_authorization_required_code() {
  887.     return is_user_logged_in() ? 403 : 401;
  888. }
  889.  
  890. /**
  891.  * Validate a request argument based on details registered to the route.
  892.  *
  893.  * @since 4.7.0
  894.  *
  895.  * @param  mixed            $value
  896.  * @param  WP_REST_Request  $request
  897.  * @param  string           $param
  898.  * @return WP_Error|boolean
  899.  */
  900. function rest_validate_request_arg( $value, $request, $param ) {
  901.     $attributes = $request->get_attributes();
  902.     if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
  903.         return true;
  904.     }
  905.     $args = $attributes['args'][ $param ];
  906.  
  907.     return rest_validate_value_from_schema( $value, $args, $param );
  908. }
  909.  
  910. /**
  911.  * Sanitize a request argument based on details registered to the route.
  912.  *
  913.  * @since 4.7.0
  914.  *
  915.  * @param  mixed            $value
  916.  * @param  WP_REST_Request  $request
  917.  * @param  string           $param
  918.  * @return mixed
  919.  */
  920. function rest_sanitize_request_arg( $value, $request, $param ) {
  921.     $attributes = $request->get_attributes();
  922.     if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
  923.         return $value;
  924.     }
  925.     $args = $attributes['args'][ $param ];
  926.  
  927.     return rest_sanitize_value_from_schema( $value, $args );
  928. }
  929.  
  930. /**
  931.  * Parse a request argument based on details registered to the route.
  932.  *
  933.  * Runs a validation check and sanitizes the value, primarily to be used via
  934.  * the `sanitize_callback` arguments in the endpoint args registration.
  935.  *
  936.  * @since 4.7.0
  937.  *
  938.  * @param  mixed            $value
  939.  * @param  WP_REST_Request  $request
  940.  * @param  string           $param
  941.  * @return mixed
  942.  */
  943. function rest_parse_request_arg( $value, $request, $param ) {
  944.     $is_valid = rest_validate_request_arg( $value, $request, $param );
  945.  
  946.     if ( is_wp_error( $is_valid ) ) {
  947.         return $is_valid;
  948.     }
  949.  
  950.     $value = rest_sanitize_request_arg( $value, $request, $param );
  951.  
  952.     return $value;
  953. }
  954.  
  955. /**
  956.  * Determines if an IP address is valid.
  957.  *
  958.  * Handles both IPv4 and IPv6 addresses.
  959.  *
  960.  * @since 4.7.0
  961.  *
  962.  * @param  string $ip IP address.
  963.  * @return string|false The valid IP address, otherwise false.
  964.  */
  965. function rest_is_ip_address( $ip ) {
  966.     $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]?)$/';
  967.  
  968.     if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
  969.         return false;
  970.     }
  971.  
  972.     return $ip;
  973. }
  974.  
  975. /**
  976.  * Changes a boolean-like value into the proper boolean value.
  977.  *
  978.  * @since 4.7.0
  979.  *
  980.  * @param bool|string|int $value The value being evaluated.
  981.  * @return boolean Returns the proper associated boolean value.
  982.  */
  983. function rest_sanitize_boolean( $value ) {
  984.     // String values are translated to `true`; make sure 'false' is false.
  985.     if ( is_string( $value )  ) {
  986.         $value = strtolower( $value );
  987.         if ( in_array( $value, array( 'false', '0' ), true ) ) {
  988.             $value = false;
  989.         }
  990.     }
  991.  
  992.     // Everything else will map nicely to boolean.
  993.     return (boolean) $value;
  994. }
  995.  
  996. /**
  997.  * Determines if a given value is boolean-like.
  998.  *
  999.  * @since 4.7.0
  1000.  *
  1001.  * @param bool|string $maybe_bool The value being evaluated.
  1002.  * @return boolean True if a boolean, otherwise false.
  1003.  */
  1004. function rest_is_boolean( $maybe_bool ) {
  1005.     if ( is_bool( $maybe_bool ) ) {
  1006.         return true;
  1007.     }
  1008.  
  1009.     if ( is_string( $maybe_bool ) ) {
  1010.         $maybe_bool = strtolower( $maybe_bool );
  1011.  
  1012.         $valid_boolean_values = array(
  1013.             'false',
  1014.             'true',
  1015.             '0',
  1016.             '1',
  1017.         );
  1018.  
  1019.         return in_array( $maybe_bool, $valid_boolean_values, true );
  1020.     }
  1021.  
  1022.     if ( is_int( $maybe_bool ) ) {
  1023.         return in_array( $maybe_bool, array( 0, 1 ), true );
  1024.     }
  1025.  
  1026.     return false;
  1027. }
  1028.  
  1029. /**
  1030.  * Retrieves the avatar urls in various sizes based on a given email address.
  1031.  *
  1032.  * @since 4.7.0
  1033.  *
  1034.  * @see get_avatar_url()
  1035.  *
  1036.  * @param string $email Email address.
  1037.  * @return array $urls Gravatar url for each size.
  1038.  */
  1039. function rest_get_avatar_urls( $email ) {
  1040.     $avatar_sizes = rest_get_avatar_sizes();
  1041.  
  1042.     $urls = array();
  1043.     foreach ( $avatar_sizes as $size ) {
  1044.         $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
  1045.     }
  1046.  
  1047.     return $urls;
  1048. }
  1049.  
  1050. /**
  1051.  * Retrieves the pixel sizes for avatars.
  1052.  *
  1053.  * @since 4.7.0
  1054.  *
  1055.  * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
  1056.  */
  1057. function rest_get_avatar_sizes() {
  1058.     /**
  1059.      * Filters the REST avatar sizes.
  1060.      *
  1061.      * Use this filter to adjust the array of sizes returned by the
  1062.      * `rest_get_avatar_sizes` function.
  1063.      *
  1064.      * @since 4.4.0
  1065.      *
  1066.      * @param array $sizes An array of int values that are the pixel sizes for avatars.
  1067.      *                     Default `[ 24, 48, 96 ]`.
  1068.      */
  1069.     return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
  1070. }
  1071.  
  1072. /**
  1073.  * Validate a value based on a schema.
  1074.  *
  1075.  * @since 4.7.0
  1076.  *
  1077.  * @param mixed  $value The value to validate.
  1078.  * @param array  $args  Schema array to use for validation.
  1079.  * @param string $param The parameter name, used in error messages.
  1080.  * @return true|WP_Error
  1081.  */
  1082. function rest_validate_value_from_schema( $value, $args, $param = '' ) {
  1083.     if ( 'array' === $args['type'] ) {
  1084.         if ( ! is_array( $value ) ) {
  1085.             $value = preg_split( '/[\s,]+/', $value );
  1086.         }
  1087.         if ( ! wp_is_numeric_array( $value ) ) {
  1088.             /* translators: 1: parameter, 2: type name */
  1089.             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  1090.         }
  1091.         foreach ( $value as $index => $v ) {
  1092.             $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  1093.             if ( is_wp_error( $is_valid ) ) {
  1094.                 return $is_valid;
  1095.             }
  1096.         }
  1097.     }
  1098.  
  1099.     if ( 'object' === $args['type'] ) {
  1100.         if ( $value instanceof stdClass ) {
  1101.             $value = (array) $value;
  1102.         }
  1103.         if ( ! is_array( $value ) ) {
  1104.             /* translators: 1: parameter, 2: type name */
  1105.             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
  1106.         }
  1107.  
  1108.         foreach ( $value as $property => $v ) {
  1109.             if ( isset( $args['properties'][ $property ] ) ) {
  1110.                 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  1111.                 if ( is_wp_error( $is_valid ) ) {
  1112.                     return $is_valid;
  1113.                 }
  1114.             } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
  1115.                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
  1116.             }
  1117.         }
  1118.     }
  1119.  
  1120.     if ( ! empty( $args['enum'] ) ) {
  1121.         if ( ! in_array( $value, $args['enum'], true ) ) {
  1122.             /* translators: 1: parameter, 2: list of valid values */
  1123.             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
  1124.         }
  1125.     }
  1126.  
  1127.     if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) {
  1128.         /* translators: 1: parameter, 2: type name */
  1129.         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
  1130.     }
  1131.  
  1132.     if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
  1133.         /* translators: 1: parameter, 2: type name */
  1134.         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
  1135.     }
  1136.  
  1137.     if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
  1138.         /* translators: 1: parameter, 2: type name */
  1139.         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) );
  1140.     }
  1141.  
  1142.     if ( 'string' === $args['type'] && ! is_string( $value ) ) {
  1143.         /* translators: 1: parameter, 2: type name */
  1144.         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
  1145.     }
  1146.  
  1147.     if ( isset( $args['format'] ) ) {
  1148.         switch ( $args['format'] ) {
  1149.             case 'date-time' :
  1150.                 if ( ! rest_parse_date( $value ) ) {
  1151.                     return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
  1152.                 }
  1153.                 break;
  1154.  
  1155.             case 'email' :
  1156.                 if ( ! is_email( $value ) ) {
  1157.                     return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
  1158.                 }
  1159.                 break;
  1160.             case 'ip' :
  1161.                 if ( ! rest_is_ip_address( $value ) ) {
  1162.                     /* translators: %s: IP address */
  1163.                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
  1164.                 }
  1165.                 break;
  1166.         }
  1167.     }
  1168.  
  1169.     if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
  1170.         if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
  1171.             if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
  1172.                 /* translators: 1: parameter, 2: minimum number */
  1173.                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
  1174.             } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
  1175.                 /* translators: 1: parameter, 2: minimum number */
  1176.                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
  1177.             }
  1178.         } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
  1179.             if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
  1180.                 /* translators: 1: parameter, 2: maximum number */
  1181.                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
  1182.             } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
  1183.                 /* translators: 1: parameter, 2: maximum number */
  1184.                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
  1185.             }
  1186.         } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
  1187.             if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1188.                 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
  1189.                     /* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1190.                     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'] ) );
  1191.                 }
  1192.             } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1193.                 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
  1194.                     /* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1195.                     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'] ) );
  1196.                 }
  1197.             } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1198.                 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
  1199.                     /* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1200.                     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'] ) );
  1201.                 }
  1202.             } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1203.                 if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
  1204.                     /* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1205.                     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'] ) );
  1206.                 }
  1207.             }
  1208.         }
  1209.     }
  1210.  
  1211.     return true;
  1212. }
  1213.  
  1214. /**
  1215.  * Sanitize a value based on a schema.
  1216.  *
  1217.  * @since 4.7.0
  1218.  *
  1219.  * @param mixed $value The value to sanitize.
  1220.  * @param array $args  Schema array to use for sanitization.
  1221.  * @return true|WP_Error
  1222.  */
  1223. function rest_sanitize_value_from_schema( $value, $args ) {
  1224.     if ( 'array' === $args['type'] ) {
  1225.         if ( empty( $args['items'] ) ) {
  1226.             return (array) $value;
  1227.         }
  1228.         if ( ! is_array( $value ) ) {
  1229.             $value = preg_split( '/[\s,]+/', $value );
  1230.         }
  1231.         foreach ( $value as $index => $v ) {
  1232.             $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
  1233.         }
  1234.         // Normalize to numeric array so nothing unexpected
  1235.         // is in the keys.
  1236.         $value = array_values( $value );
  1237.         return $value;
  1238.     }
  1239.  
  1240.     if ( 'object' === $args['type'] ) {
  1241.         if ( $value instanceof stdClass ) {
  1242.             $value = (array) $value;
  1243.         }
  1244.         if ( ! is_array( $value ) ) {
  1245.             return array();
  1246.         }
  1247.  
  1248.         foreach ( $value as $property => $v ) {
  1249.             if ( isset( $args['properties'][ $property ] ) ) {
  1250.                 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
  1251.             } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
  1252.                 unset( $value[ $property ] );
  1253.             }
  1254.         }
  1255.  
  1256.         return $value;
  1257.     }
  1258.  
  1259.     if ( 'integer' === $args['type'] ) {
  1260.         return (int) $value;
  1261.     }
  1262.  
  1263.     if ( 'number' === $args['type'] ) {
  1264.         return (float) $value;
  1265.     }
  1266.  
  1267.     if ( 'boolean' === $args['type'] ) {
  1268.         return rest_sanitize_boolean( $value );
  1269.     }
  1270.  
  1271.     if ( isset( $args['format'] ) ) {
  1272.         switch ( $args['format'] ) {
  1273.             case 'date-time' :
  1274.                 return sanitize_text_field( $value );
  1275.  
  1276.             case 'email' :
  1277.                 /*
  1278.                  * sanitize_email() validates, which would be unexpected.
  1279.                  */
  1280.                 return sanitize_text_field( $value );
  1281.  
  1282.             case 'uri' :
  1283.                 return esc_url_raw( $value );
  1284.  
  1285.             case 'ip' :
  1286.                 return sanitize_text_field( $value );
  1287.         }
  1288.     }
  1289.  
  1290.     if ( 'string' === $args['type'] ) {
  1291.         return strval( $value );
  1292.     }
  1293.  
  1294.     return $value;
  1295. }
  1296.