wp/wp-includes/rest-api.php
changeset 16 a86126ab1dd4
parent 13 d255fe9cd479
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    18  * Registers a REST API route.
    18  * Registers a REST API route.
    19  *
    19  *
    20  * Note: Do not use before the {@see 'rest_api_init'} hook.
    20  * Note: Do not use before the {@see 'rest_api_init'} hook.
    21  *
    21  *
    22  * @since 4.4.0
    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.
    23  * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook.
       
    24  * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set.
    24  *
    25  *
    25  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
    26  * @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 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  * @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  *                          multiple methods. Default empty array.
    42 	} elseif ( empty( $route ) ) {
    43 	} elseif ( empty( $route ) ) {
    43 		_doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
    44 		_doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
    44 		return false;
    45 		return false;
    45 	}
    46 	}
    46 
    47 
       
    48 	$clean_namespace = trim( $namespace, '/' );
       
    49 
       
    50 	if ( $clean_namespace !== $namespace ) {
       
    51 		_doing_it_wrong( __FUNCTION__, __( 'Namespace must not start or end with a slash.' ), '5.4.2' );
       
    52 	}
       
    53 
    47 	if ( ! did_action( 'rest_api_init' ) ) {
    54 	if ( ! did_action( 'rest_api_init' ) ) {
    48 		_doing_it_wrong(
    55 		_doing_it_wrong(
    49 			'register_rest_route',
    56 			'register_rest_route',
    50 			sprintf(
    57 			sprintf(
    51 				/* translators: %s: rest_api_init */
    58 				/* translators: %s: rest_api_init */
    71 	$defaults = array(
    78 	$defaults = array(
    72 		'methods'  => 'GET',
    79 		'methods'  => 'GET',
    73 		'callback' => null,
    80 		'callback' => null,
    74 		'args'     => array(),
    81 		'args'     => array(),
    75 	);
    82 	);
       
    83 
    76 	foreach ( $args as $key => &$arg_group ) {
    84 	foreach ( $args as $key => &$arg_group ) {
    77 		if ( ! is_numeric( $key ) ) {
    85 		if ( ! is_numeric( $key ) ) {
    78 			// Route option, skip here.
    86 			// Route option, skip here.
    79 			continue;
    87 			continue;
    80 		}
    88 		}
    81 
    89 
    82 		$arg_group         = array_merge( $defaults, $arg_group );
    90 		$arg_group         = array_merge( $defaults, $arg_group );
    83 		$arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
    91 		$arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
    84 	}
    92 
    85 
    93 		if ( ! isset( $arg_group['permission_callback'] ) ) {
    86 	$full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
    94 			_doing_it_wrong(
    87 	rest_get_server()->register_route( $namespace, $full_route, $args, $override );
    95 				__FUNCTION__,
       
    96 				sprintf(
       
    97 					/* translators: 1. The REST API route being registered. 2. The argument name. 3. The suggested function name. */
       
    98 					__( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ),
       
    99 					'<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
       
   100 					'<code>permission_callback</code>',
       
   101 					'<code>__return_true</code>'
       
   102 				),
       
   103 				'5.5.0'
       
   104 			);
       
   105 		}
       
   106 	}
       
   107 
       
   108 	$full_route = '/' . $clean_namespace . '/' . trim( $route, '/' );
       
   109 	rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override );
    88 	return true;
   110 	return true;
    89 }
   111 }
    90 
   112 
    91 /**
   113 /**
    92  * Registers a new field on an existing WordPress object type.
   114  * Registers a new field on an existing WordPress object type.
    96  * @global array $wp_rest_additional_fields Holds registered fields, organized
   118  * @global array $wp_rest_additional_fields Holds registered fields, organized
    97  *                                          by object type.
   119  *                                          by object type.
    98  *
   120  *
    99  * @param string|array $object_type Object(s) the field is being registered
   121  * @param string|array $object_type Object(s) the field is being registered
   100  *                                  to, "post"|"term"|"comment" etc.
   122  *                                  to, "post"|"term"|"comment" etc.
   101  * @param string $attribute         The attribute name.
   123  * @param string       $attribute   The attribute name.
   102  * @param array  $args {
   124  * @param array        $args {
   103  *     Optional. An array of arguments used to handle the registered field.
   125  *     Optional. An array of arguments used to handle the registered field.
   104  *
   126  *
   105  *     @type string|array|null $get_callback    Optional. The callback function used to retrieve the field
   127  *     @type callable|null $get_callback    Optional. The callback function used to retrieve the field value. Default is
   106  *                                              value. Default is 'null', the field will not be returned in
   128  *                                          'null', the field will not be returned in the response. The function will
   107  *                                              the response.
   129  *                                          be passed the prepared object data.
   108  *     @type string|array|null $update_callback Optional. The callback function used to set and update the
   130  *     @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
   109  *                                              field value. Default is 'null', the value cannot be set or
   131  *                                          is 'null', the value cannot be set or updated. The function will be passed
   110  *                                              updated.
   132  *                                          the model object, like WP_Post.
   111  *     @type string|array|null $schema          Optional. The callback function used to create the schema for
   133  *     @type array|null $schema             Optional. The callback function used to create the schema for this field.
   112  *                                              this field. Default is 'null', no schema entry will be returned.
   134  *                                          Default is 'null', no schema entry will be returned.
   113  * }
   135  * }
   114  */
   136  */
   115 function register_rest_field( $object_type, $attribute, $args = array() ) {
   137 function register_rest_field( $object_type, $attribute, $args = array() ) {
   116 	$defaults = array(
   138 	$defaults = array(
   117 		'get_callback'    => null,
   139 		'get_callback'    => null,
   149  * Adds REST rewrite rules.
   171  * Adds REST rewrite rules.
   150  *
   172  *
   151  * @since 4.4.0
   173  * @since 4.4.0
   152  *
   174  *
   153  * @see add_rewrite_rule()
   175  * @see add_rewrite_rule()
   154  * @global WP_Rewrite $wp_rewrite
   176  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   155  */
   177  */
   156 function rest_api_register_rewrites() {
   178 function rest_api_register_rewrites() {
   157 	global $wp_rewrite;
   179 	global $wp_rewrite;
   158 
   180 
   159 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
   181 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
   169  * to make testing and disabling these filters easier.
   191  * to make testing and disabling these filters easier.
   170  *
   192  *
   171  * @since 4.4.0
   193  * @since 4.4.0
   172  */
   194  */
   173 function rest_api_default_filters() {
   195 function rest_api_default_filters() {
   174 	// Deprecated reporting.
   196 	if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
   175 	add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
   197 		// Deprecated reporting.
   176 	add_filter( 'deprecated_function_trigger_error', '__return_false' );
   198 		add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
   177 	add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
   199 		add_filter( 'deprecated_function_trigger_error', '__return_false' );
   178 	add_filter( 'deprecated_argument_trigger_error', '__return_false' );
   200 		add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
       
   201 		add_filter( 'deprecated_argument_trigger_error', '__return_false' );
       
   202 		add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 );
       
   203 		add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
       
   204 	}
   179 
   205 
   180 	// Default serving.
   206 	// Default serving.
   181 	add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
   207 	add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
   182 	add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
   208 	add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
   183 	add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
   209 	add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
   190  *
   216  *
   191  * @since 4.7.0
   217  * @since 4.7.0
   192  */
   218  */
   193 function create_initial_rest_routes() {
   219 function create_initial_rest_routes() {
   194 	foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
   220 	foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
   195 		$class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
   221 		$controller = $post_type->get_rest_controller();
   196 
   222 
   197 		if ( ! class_exists( $class ) ) {
   223 		if ( ! $controller ) {
   198 			continue;
       
   199 		}
       
   200 		$controller = new $class( $post_type->name );
       
   201 		if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
       
   202 			continue;
   224 			continue;
   203 		}
   225 		}
   204 
   226 
   205 		$controller->register_routes();
   227 		$controller->register_routes();
   206 
   228 
   227 	$controller = new WP_REST_Taxonomies_Controller;
   249 	$controller = new WP_REST_Taxonomies_Controller;
   228 	$controller->register_routes();
   250 	$controller->register_routes();
   229 
   251 
   230 	// Terms.
   252 	// Terms.
   231 	foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
   253 	foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
   232 		$class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
   254 		$controller = $taxonomy->get_rest_controller();
   233 
   255 
   234 		if ( ! class_exists( $class ) ) {
   256 		if ( ! $controller ) {
   235 			continue;
       
   236 		}
       
   237 		$controller = new $class( $taxonomy->name );
       
   238 		if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
       
   239 			continue;
   257 			continue;
   240 		}
   258 		}
   241 
   259 
   242 		$controller->register_routes();
   260 		$controller->register_routes();
   243 	}
   261 	}
   266 
   284 
   267 	// Block Renderer.
   285 	// Block Renderer.
   268 	$controller = new WP_REST_Block_Renderer_Controller;
   286 	$controller = new WP_REST_Block_Renderer_Controller;
   269 	$controller->register_routes();
   287 	$controller->register_routes();
   270 
   288 
       
   289 	// Block Types.
       
   290 	$controller = new WP_REST_Block_Types_Controller();
       
   291 	$controller->register_routes();
       
   292 
   271 	// Settings.
   293 	// Settings.
   272 	$controller = new WP_REST_Settings_Controller;
   294 	$controller = new WP_REST_Settings_Controller;
   273 	$controller->register_routes();
   295 	$controller->register_routes();
   274 
   296 
   275 	// Themes.
   297 	// Themes.
   276 	$controller = new WP_REST_Themes_Controller;
   298 	$controller = new WP_REST_Themes_Controller;
   277 	$controller->register_routes();
   299 	$controller->register_routes();
   278 
   300 
       
   301 	// Plugins.
       
   302 	$controller = new WP_REST_Plugins_Controller();
       
   303 	$controller->register_routes();
       
   304 
       
   305 	// Block Directory.
       
   306 	$controller = new WP_REST_Block_Directory_Controller();
       
   307 	$controller->register_routes();
       
   308 
   279 }
   309 }
   280 
   310 
   281 /**
   311 /**
   282  * Loads the REST API.
   312  * Loads the REST API.
   283  *
   313  *
   284  * @since 4.4.0
   314  * @since 4.4.0
   285  *
   315  *
   286  * @global WP             $wp             Current WordPress environment instance.
   316  * @global WP $wp Current WordPress environment instance.
   287  */
   317  */
   288 function rest_api_loaded() {
   318 function rest_api_loaded() {
   289 	if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
   319 	if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
   290 		return;
   320 		return;
   291 	}
   321 	}
   336  * Note: The returned URL is NOT escaped.
   366  * Note: The returned URL is NOT escaped.
   337  *
   367  *
   338  * @since 4.4.0
   368  * @since 4.4.0
   339  *
   369  *
   340  * @todo Check if this is even necessary
   370  * @todo Check if this is even necessary
   341  * @global WP_Rewrite $wp_rewrite
   371  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   342  *
   372  *
   343  * @param int    $blog_id Optional. Blog ID. Default of null returns URL for current blog.
   373  * @param int    $blog_id Optional. Blog ID. Default of null returns URL for current blog.
   344  * @param string $path    Optional. REST route. Default '/'.
   374  * @param string $path    Optional. REST route. Default '/'.
   345  * @param string $scheme  Optional. Sanitization scheme. Default 'rest'.
   375  * @param string $scheme  Optional. Sanitization scheme. Default 'rest'.
   346  * @return string Full URL to the endpoint.
   376  * @return string Full URL to the endpoint.
   362 		}
   392 		}
   363 
   393 
   364 		$url .= $path;
   394 		$url .= $path;
   365 	} else {
   395 	} else {
   366 		$url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
   396 		$url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
   367 		// nginx only allows HTTP/1.0 methods when redirecting from / to /index.php
   397 		// nginx only allows HTTP/1.0 methods when redirecting from / to /index.php.
   368 		// To work around this, we manually add index.php to the URL, avoiding the redirect.
   398 		// To work around this, we manually add index.php to the URL, avoiding the redirect.
   369 		if ( 'index.php' !== substr( $url, 9 ) ) {
   399 		if ( 'index.php' !== substr( $url, 9 ) ) {
   370 			$url .= 'index.php';
   400 			$url .= 'index.php';
   371 		}
   401 		}
   372 
   402 
   373 		$url = add_query_arg( 'rest_route', $path, $url );
   403 		$url = add_query_arg( 'rest_route', $path, $url );
   374 	}
   404 	}
   375 
   405 
   376 	if ( is_ssl() ) {
   406 	if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) {
   377 		// If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
   407 		// If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
   378 		if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
   408 		if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) {
   379 			$url = set_url_scheme( $url, 'https' );
   409 			$url = set_url_scheme( $url, 'https' );
   380 		}
   410 		}
   381 	}
   411 	}
   382 
   412 
   383 	if ( is_admin() && force_ssl_admin() ) {
   413 	if ( is_admin() && force_ssl_admin() ) {
   384 		// In this situation the home URL may be http:, and `is_ssl()` may be
   414 		/*
   385 		// false, but the admin is served over https: (one way or another), so
   415 		 * In this situation the home URL may be http:, and `is_ssl()` may be false,
   386 		// REST API usage will be blocked by browsers unless it is also served
   416 		 * but the admin is served over https: (one way or another), so REST API usage
   387 		// over HTTPS.
   417 		 * will be blocked by browsers unless it is also served over HTTPS.
       
   418 		 */
   388 		$url = set_url_scheme( $url, 'https' );
   419 		$url = set_url_scheme( $url, 'https' );
   389 	}
   420 	}
   390 
   421 
   391 	/**
   422 	/**
   392 	 * Filters the REST URL.
   423 	 * Filters the REST URL.
   409  * Note: The returned URL is NOT escaped.
   440  * Note: The returned URL is NOT escaped.
   410  *
   441  *
   411  * @since 4.4.0
   442  * @since 4.4.0
   412  *
   443  *
   413  * @param string $path   Optional. REST route. Default empty.
   444  * @param string $path   Optional. REST route. Default empty.
   414  * @param string $scheme Optional. Sanitization scheme. Default 'json'.
   445  * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
   415  * @return string Full URL to the endpoint.
   446  * @return string Full URL to the endpoint.
   416  */
   447  */
   417 function rest_url( $path = '', $scheme = 'json' ) {
   448 function rest_url( $path = '', $scheme = 'rest' ) {
   418 	return get_rest_url( null, $path, $scheme );
   449 	return get_rest_url( null, $path, $scheme );
   419 }
   450 }
   420 
   451 
   421 /**
   452 /**
   422  * Do a REST request.
   453  * Do a REST request.
   480 
   511 
   481 /**
   512 /**
   482  * Ensures request arguments are a request object (for consistency).
   513  * Ensures request arguments are a request object (for consistency).
   483  *
   514  *
   484  * @since 4.4.0
   515  * @since 4.4.0
   485  *
   516  * @since 5.3.0 Accept string argument for the request path.
   486  * @param array|WP_REST_Request $request Request to check.
   517  *
       
   518  * @param array|string|WP_REST_Request $request Request to check.
   487  * @return WP_REST_Request REST request instance.
   519  * @return WP_REST_Request REST request instance.
   488  */
   520  */
   489 function rest_ensure_request( $request ) {
   521 function rest_ensure_request( $request ) {
   490 	if ( $request instanceof WP_REST_Request ) {
   522 	if ( $request instanceof WP_REST_Request ) {
   491 		return $request;
   523 		return $request;
   492 	}
   524 	}
   493 
   525 
       
   526 	if ( is_string( $request ) ) {
       
   527 		return new WP_REST_Request( 'GET', $request );
       
   528 	}
       
   529 
   494 	return new WP_REST_Request( 'GET', '', $request );
   530 	return new WP_REST_Request( 'GET', '', $request );
   495 }
   531 }
   496 
   532 
   497 /**
   533 /**
   498  * Ensures a REST response is a response object (for consistency).
   534  * Ensures a REST response is a response object (for consistency).
   499  *
   535  *
   500  * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
   536  * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc
   501  * without needing to double-check the object. Will also allow WP_Error to indicate error
   537  * without needing to double-check the object. Will also allow WP_Error to indicate error
   502  * responses, so users should immediately check for this value.
   538  * responses, so users should immediately check for this value.
   503  *
   539  *
   504  * @since 4.4.0
   540  * @since 4.4.0
   505  *
   541  *
   506  * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
   542  * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check.
   507  * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response
   543  * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response
   508  *                                is already an instance, WP_HTTP_Response, otherwise
   544  *                                   is already an instance, WP_REST_Response, otherwise
   509  *                                returns a new WP_REST_Response instance.
   545  *                                   returns a new WP_REST_Response instance.
   510  */
   546  */
   511 function rest_ensure_response( $response ) {
   547 function rest_ensure_response( $response ) {
   512 	if ( is_wp_error( $response ) ) {
   548 	if ( is_wp_error( $response ) ) {
   513 		return $response;
   549 		return $response;
   514 	}
   550 	}
   515 
   551 
       
   552 	if ( $response instanceof WP_REST_Response ) {
       
   553 		return $response;
       
   554 	}
       
   555 
       
   556 	// While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide
       
   557 	// all the required methods used in WP_REST_Server::dispatch().
   516 	if ( $response instanceof WP_HTTP_Response ) {
   558 	if ( $response instanceof WP_HTTP_Response ) {
   517 		return $response;
   559 		return new WP_REST_Response(
       
   560 			$response->get_data(),
       
   561 			$response->get_status(),
       
   562 			$response->get_headers()
       
   563 		);
   518 	}
   564 	}
   519 
   565 
   520 	return new WP_REST_Response( $response );
   566 	return new WP_REST_Response( $response );
   521 }
   567 }
   522 
   568 
   532 function rest_handle_deprecated_function( $function, $replacement, $version ) {
   578 function rest_handle_deprecated_function( $function, $replacement, $version ) {
   533 	if ( ! WP_DEBUG || headers_sent() ) {
   579 	if ( ! WP_DEBUG || headers_sent() ) {
   534 		return;
   580 		return;
   535 	}
   581 	}
   536 	if ( ! empty( $replacement ) ) {
   582 	if ( ! empty( $replacement ) ) {
   537 		/* translators: 1: function name, 2: WordPress version number, 3: new function name */
   583 		/* translators: 1: Function name, 2: WordPress version number, 3: New function name. */
   538 		$string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
   584 		$string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
   539 	} else {
   585 	} else {
   540 		/* translators: 1: function name, 2: WordPress version number */
   586 		/* translators: 1: Function name, 2: WordPress version number. */
   541 		$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
   587 		$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
   542 	}
   588 	}
   543 
   589 
   544 	header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
   590 	header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
   545 }
   591 }
   555  */
   601  */
   556 function rest_handle_deprecated_argument( $function, $message, $version ) {
   602 function rest_handle_deprecated_argument( $function, $message, $version ) {
   557 	if ( ! WP_DEBUG || headers_sent() ) {
   603 	if ( ! WP_DEBUG || headers_sent() ) {
   558 		return;
   604 		return;
   559 	}
   605 	}
   560 	if ( ! empty( $message ) ) {
   606 	if ( $message ) {
   561 		/* translators: 1: function name, 2: WordPress version number, 3: error message */
   607 		/* translators: 1: Function name, 2: WordPress version number, 3: Error message. */
   562 		$string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
   608 		$string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
   563 	} else {
   609 	} else {
   564 		/* translators: 1: function name, 2: WordPress version number */
   610 		/* translators: 1: Function name, 2: WordPress version number. */
   565 		$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
   611 		$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
   566 	}
   612 	}
   567 
   613 
   568 	header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
   614 	header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
       
   615 }
       
   616 
       
   617 /**
       
   618  * Handles _doing_it_wrong errors.
       
   619  *
       
   620  * @since 5.5.0
       
   621  *
       
   622  * @param string      $function The function that was called.
       
   623  * @param string      $message  A message explaining what has been done incorrectly.
       
   624  * @param string|null $version  The version of WordPress where the message was added.
       
   625  */
       
   626 function rest_handle_doing_it_wrong( $function, $message, $version ) {
       
   627 	if ( ! WP_DEBUG || headers_sent() ) {
       
   628 		return;
       
   629 	}
       
   630 
       
   631 	if ( $version ) {
       
   632 		/* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */
       
   633 		$string = __( '%1$s (since %2$s; %3$s)' );
       
   634 		$string = sprintf( $string, $function, $version, $message );
       
   635 	} else {
       
   636 		/* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */
       
   637 		$string = __( '%1$s (%2$s)' );
       
   638 		$string = sprintf( $string, $function, $message );
       
   639 	}
       
   640 
       
   641 	header( sprintf( 'X-WP-DoingItWrong: %s', $string ) );
   569 }
   642 }
   570 
   643 
   571 /**
   644 /**
   572  * Sends Cross-Origin Resource Sharing headers with API requests.
   645  * Sends Cross-Origin Resource Sharing headers with API requests.
   573  *
   646  *
   578  */
   651  */
   579 function rest_send_cors_headers( $value ) {
   652 function rest_send_cors_headers( $value ) {
   580 	$origin = get_http_origin();
   653 	$origin = get_http_origin();
   581 
   654 
   582 	if ( $origin ) {
   655 	if ( $origin ) {
   583 		// Requests from file:// and data: URLs send "Origin: null"
   656 		// Requests from file:// and data: URLs send "Origin: null".
   584 		if ( 'null' !== $origin ) {
   657 		if ( 'null' !== $origin ) {
   585 			$origin = esc_url_raw( $origin );
   658 			$origin = esc_url_raw( $origin );
   586 		}
   659 		}
   587 		header( 'Access-Control-Allow-Origin: ' . $origin );
   660 		header( 'Access-Control-Allow-Origin: ' . $origin );
   588 		header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
   661 		header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
   589 		header( 'Access-Control-Allow-Credentials: true' );
   662 		header( 'Access-Control-Allow-Credentials: true' );
   590 		header( 'Vary: Origin', false );
   663 		header( 'Vary: Origin', false );
   591 	} elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
   664 	} elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
   592 		header( 'Vary: Origin' );
   665 		header( 'Vary: Origin', false );
   593 	}
   666 	}
   594 
   667 
   595 	return $value;
   668 	return $value;
   596 }
   669 }
   597 
   670 
   629 				$args[ $param ] = $value;
   702 				$args[ $param ] = $value;
   630 			}
   703 			}
   631 		}
   704 		}
   632 
   705 
   633 		foreach ( $endpoints as $endpoint ) {
   706 		foreach ( $endpoints as $endpoint ) {
   634 			// Remove the redundant preg_match argument.
   707 			// Remove the redundant preg_match() argument.
   635 			unset( $args[0] );
   708 			unset( $args[0] );
   636 
   709 
   637 			$request->set_url_params( $args );
   710 			$request->set_url_params( $args );
   638 			$request->set_attributes( $endpoint );
   711 			$request->set_attributes( $endpoint );
   639 		}
   712 		}
   692 
   765 
   693 	return $response;
   766 	return $response;
   694 }
   767 }
   695 
   768 
   696 /**
   769 /**
       
   770  * Recursively computes the intersection of arrays using keys for comparison.
       
   771  *
       
   772  * @since 5.3.0
       
   773  *
       
   774  * @param array $array1 The array with master keys to check.
       
   775  * @param array $array2 An array to compare keys against.
       
   776  * @return array An associative array containing all the entries of array1 which have keys
       
   777  *               that are present in all arguments.
       
   778  */
       
   779 function _rest_array_intersect_key_recursive( $array1, $array2 ) {
       
   780 	$array1 = array_intersect_key( $array1, $array2 );
       
   781 	foreach ( $array1 as $key => $value ) {
       
   782 		if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
       
   783 			$array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
       
   784 		}
       
   785 	}
       
   786 	return $array1;
       
   787 }
       
   788 
       
   789 /**
   697  * Filter the API response to include only a white-listed set of response object fields.
   790  * Filter the API response to include only a white-listed set of response object fields.
   698  *
   791  *
   699  * @since 4.8.0
   792  * @since 4.8.0
   700  *
   793  *
   701  * @param WP_REST_Response $response Current response being served.
   794  * @param WP_REST_Response $response Current response being served.
   702  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
   795  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
   703  * @param WP_REST_Request  $request  The request that was used to make current response.
   796  * @param WP_REST_Request  $request  The request that was used to make current response.
   704  *
       
   705  * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
   797  * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
   706  */
   798  */
   707 function rest_filter_response_fields( $response, $server, $request ) {
   799 function rest_filter_response_fields( $response, $server, $request ) {
   708 	if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
   800 	if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
   709 		return $response;
   801 		return $response;
   718 	}
   810 	}
   719 
   811 
   720 	// Trim off outside whitespace from the comma delimited list.
   812 	// Trim off outside whitespace from the comma delimited list.
   721 	$fields = array_map( 'trim', $fields );
   813 	$fields = array_map( 'trim', $fields );
   722 
   814 
   723 	$fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) );
   815 	// Create nested array of accepted field hierarchy.
       
   816 	$fields_as_keyed = array();
       
   817 	foreach ( $fields as $field ) {
       
   818 		$parts = explode( '.', $field );
       
   819 		$ref   = &$fields_as_keyed;
       
   820 		while ( count( $parts ) > 1 ) {
       
   821 			$next = array_shift( $parts );
       
   822 			if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) {
       
   823 				// Skip any sub-properties if their parent prop is already marked for inclusion.
       
   824 				break 2;
       
   825 			}
       
   826 			$ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array();
       
   827 			$ref          = &$ref[ $next ];
       
   828 		}
       
   829 		$last         = array_shift( $parts );
       
   830 		$ref[ $last ] = true;
       
   831 	}
   724 
   832 
   725 	if ( wp_is_numeric_array( $data ) ) {
   833 	if ( wp_is_numeric_array( $data ) ) {
   726 		$new_data = array();
   834 		$new_data = array();
   727 		foreach ( $data as $item ) {
   835 		foreach ( $data as $item ) {
   728 			$new_data[] = array_intersect_key( $item, $fields_as_keyed );
   836 			$new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
   729 		}
   837 		}
   730 	} else {
   838 	} else {
   731 		$new_data = array_intersect_key( $data, $fields_as_keyed );
   839 		$new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
   732 	}
   840 	}
   733 
   841 
   734 	$response->set_data( $new_data );
   842 	$response->set_data( $new_data );
   735 
   843 
   736 	return $response;
   844 	return $response;
       
   845 }
       
   846 
       
   847 /**
       
   848  * Given an array of fields to include in a response, some of which may be
       
   849  * `nested.fields`, determine whether the provided field should be included
       
   850  * in the response body.
       
   851  *
       
   852  * If a parent field is passed in, the presence of any nested field within
       
   853  * that parent will cause the method to return `true`. For example "title"
       
   854  * will return true if any of `title`, `title.raw` or `title.rendered` is
       
   855  * provided.
       
   856  *
       
   857  * @since 5.3.0
       
   858  *
       
   859  * @param string $field  A field to test for inclusion in the response body.
       
   860  * @param array  $fields An array of string fields supported by the endpoint.
       
   861  * @return bool Whether to include the field or not.
       
   862  */
       
   863 function rest_is_field_included( $field, $fields ) {
       
   864 	if ( in_array( $field, $fields, true ) ) {
       
   865 		return true;
       
   866 	}
       
   867 
       
   868 	foreach ( $fields as $accepted_field ) {
       
   869 		// Check to see if $field is the parent of any item in $fields.
       
   870 		// A field "parent" should be accepted if "parent.child" is accepted.
       
   871 		if ( strpos( $accepted_field, "$field." ) === 0 ) {
       
   872 			return true;
       
   873 		}
       
   874 		// Conversely, if "parent" is accepted, all "parent.child" fields
       
   875 		// should also be accepted.
       
   876 		if ( strpos( $field, "$accepted_field." ) === 0 ) {
       
   877 			return true;
       
   878 		}
       
   879 	}
       
   880 
       
   881 	return false;
   737 }
   882 }
   738 
   883 
   739 /**
   884 /**
   740  * Adds the REST API URL to the WP RSD endpoint.
   885  * Adds the REST API URL to the WP RSD endpoint.
   741  *
   886  *
   766 
   911 
   767 	if ( empty( $api_root ) ) {
   912 	if ( empty( $api_root ) ) {
   768 		return;
   913 		return;
   769 	}
   914 	}
   770 
   915 
   771 	echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
   916 	printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
       
   917 
       
   918 	$resource = rest_get_queried_resource_route();
       
   919 
       
   920 	if ( $resource ) {
       
   921 		printf( '<link rel="alternate" type="application/json" href="%s" />', esc_url( rest_url( $resource ) ) );
       
   922 	}
   772 }
   923 }
   773 
   924 
   774 /**
   925 /**
   775  * Sends a Link header for the REST API.
   926  * Sends a Link header for the REST API.
   776  *
   927  *
   785 
   936 
   786 	if ( empty( $api_root ) ) {
   937 	if ( empty( $api_root ) ) {
   787 		return;
   938 		return;
   788 	}
   939 	}
   789 
   940 
   790 	header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
   941 	header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', esc_url_raw( $api_root ) ), false );
       
   942 
       
   943 	$resource = rest_get_queried_resource_route();
       
   944 
       
   945 	if ( $resource ) {
       
   946 		header( sprintf( 'Link: <%s>; rel="alternate"; type="application/json"', esc_url_raw( rest_url( $resource ) ) ), false );
       
   947 	}
   791 }
   948 }
   792 
   949 
   793 /**
   950 /**
   794  * Checks for errors when using cookie-based authentication.
   951  * Checks for errors when using cookie-based authentication.
   795  *
   952  *
   800  * @since 4.4.0
   957  * @since 4.4.0
   801  *
   958  *
   802  * @global mixed          $wp_rest_auth_cookie
   959  * @global mixed          $wp_rest_auth_cookie
   803  *
   960  *
   804  * @param WP_Error|mixed $result Error from another authentication handler,
   961  * @param WP_Error|mixed $result Error from another authentication handler,
   805  *                               null if we should handle it, or another value
   962  *                               null if we should handle it, or another value if not.
   806  *                               if not.
       
   807  * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
   963  * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
   808  */
   964  */
   809 function rest_cookie_check_errors( $result ) {
   965 function rest_cookie_check_errors( $result ) {
   810 	if ( ! empty( $result ) ) {
   966 	if ( ! empty( $result ) ) {
   811 		return $result;
   967 		return $result;
   872 
  1028 
   873 	$wp_rest_auth_cookie = true;
  1029 	$wp_rest_auth_cookie = true;
   874 }
  1030 }
   875 
  1031 
   876 /**
  1032 /**
   877  * Parses an RFC3339 time into a Unix timestamp.
  1033  * Retrieves the avatar urls in various sizes.
   878  *
       
   879  * @since 4.4.0
       
   880  *
       
   881  * @param string $date      RFC3339 timestamp.
       
   882  * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
       
   883  *                          the timestamp's timezone. Default false.
       
   884  * @return int Unix timestamp.
       
   885  */
       
   886 function rest_parse_date( $date, $force_utc = false ) {
       
   887 	if ( $force_utc ) {
       
   888 		$date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
       
   889 	}
       
   890 
       
   891 	$regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
       
   892 
       
   893 	if ( ! preg_match( $regex, $date, $matches ) ) {
       
   894 		return false;
       
   895 	}
       
   896 
       
   897 	return strtotime( $date );
       
   898 }
       
   899 
       
   900 /**
       
   901  * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
       
   902  *
       
   903  * @since 4.4.0
       
   904  *
       
   905  * @see rest_parse_date()
       
   906  *
       
   907  * @param string $date   RFC3339 timestamp.
       
   908  * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
       
   909  * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
       
   910  *                    null on failure.
       
   911  */
       
   912 function rest_get_date_with_gmt( $date, $is_utc = false ) {
       
   913 	// Whether or not the original date actually has a timezone string
       
   914 	// changes the way we need to do timezone conversion.  Store this info
       
   915 	// before parsing the date, and use it later.
       
   916 	$has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
       
   917 
       
   918 	$date = rest_parse_date( $date );
       
   919 
       
   920 	if ( empty( $date ) ) {
       
   921 		return null;
       
   922 	}
       
   923 
       
   924 	// At this point $date could either be a local date (if we were passed a
       
   925 	// *local* date without a timezone offset) or a UTC date (otherwise).
       
   926 	// Timezone conversion needs to be handled differently between these two
       
   927 	// cases.
       
   928 	if ( ! $is_utc && ! $has_timezone ) {
       
   929 		$local = date( 'Y-m-d H:i:s', $date );
       
   930 		$utc   = get_gmt_from_date( $local );
       
   931 	} else {
       
   932 		$utc   = date( 'Y-m-d H:i:s', $date );
       
   933 		$local = get_date_from_gmt( $utc );
       
   934 	}
       
   935 
       
   936 	return array( $local, $utc );
       
   937 }
       
   938 
       
   939 /**
       
   940  * Returns a contextual HTTP error code for authorization failure.
       
   941  *
  1034  *
   942  * @since 4.7.0
  1035  * @since 4.7.0
   943  *
  1036  *
   944  * @return integer 401 if the user is not logged in, 403 if the user is logged in.
       
   945  */
       
   946 function rest_authorization_required_code() {
       
   947 	return is_user_logged_in() ? 403 : 401;
       
   948 }
       
   949 
       
   950 /**
       
   951  * Validate a request argument based on details registered to the route.
       
   952  *
       
   953  * @since 4.7.0
       
   954  *
       
   955  * @param  mixed            $value
       
   956  * @param  WP_REST_Request  $request
       
   957  * @param  string           $param
       
   958  * @return WP_Error|boolean
       
   959  */
       
   960 function rest_validate_request_arg( $value, $request, $param ) {
       
   961 	$attributes = $request->get_attributes();
       
   962 	if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
       
   963 		return true;
       
   964 	}
       
   965 	$args = $attributes['args'][ $param ];
       
   966 
       
   967 	return rest_validate_value_from_schema( $value, $args, $param );
       
   968 }
       
   969 
       
   970 /**
       
   971  * Sanitize a request argument based on details registered to the route.
       
   972  *
       
   973  * @since 4.7.0
       
   974  *
       
   975  * @param  mixed            $value
       
   976  * @param  WP_REST_Request  $request
       
   977  * @param  string           $param
       
   978  * @return mixed
       
   979  */
       
   980 function rest_sanitize_request_arg( $value, $request, $param ) {
       
   981 	$attributes = $request->get_attributes();
       
   982 	if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
       
   983 		return $value;
       
   984 	}
       
   985 	$args = $attributes['args'][ $param ];
       
   986 
       
   987 	return rest_sanitize_value_from_schema( $value, $args );
       
   988 }
       
   989 
       
   990 /**
       
   991  * Parse a request argument based on details registered to the route.
       
   992  *
       
   993  * Runs a validation check and sanitizes the value, primarily to be used via
       
   994  * the `sanitize_callback` arguments in the endpoint args registration.
       
   995  *
       
   996  * @since 4.7.0
       
   997  *
       
   998  * @param  mixed            $value
       
   999  * @param  WP_REST_Request  $request
       
  1000  * @param  string           $param
       
  1001  * @return mixed
       
  1002  */
       
  1003 function rest_parse_request_arg( $value, $request, $param ) {
       
  1004 	$is_valid = rest_validate_request_arg( $value, $request, $param );
       
  1005 
       
  1006 	if ( is_wp_error( $is_valid ) ) {
       
  1007 		return $is_valid;
       
  1008 	}
       
  1009 
       
  1010 	$value = rest_sanitize_request_arg( $value, $request, $param );
       
  1011 
       
  1012 	return $value;
       
  1013 }
       
  1014 
       
  1015 /**
       
  1016  * Determines if an IP address is valid.
       
  1017  *
       
  1018  * Handles both IPv4 and IPv6 addresses.
       
  1019  *
       
  1020  * @since 4.7.0
       
  1021  *
       
  1022  * @param  string $ip IP address.
       
  1023  * @return string|false The valid IP address, otherwise false.
       
  1024  */
       
  1025 function rest_is_ip_address( $ip ) {
       
  1026 	$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]?)$/';
       
  1027 
       
  1028 	if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
       
  1029 		return false;
       
  1030 	}
       
  1031 
       
  1032 	return $ip;
       
  1033 }
       
  1034 
       
  1035 /**
       
  1036  * Changes a boolean-like value into the proper boolean value.
       
  1037  *
       
  1038  * @since 4.7.0
       
  1039  *
       
  1040  * @param bool|string|int $value The value being evaluated.
       
  1041  * @return boolean Returns the proper associated boolean value.
       
  1042  */
       
  1043 function rest_sanitize_boolean( $value ) {
       
  1044 	// String values are translated to `true`; make sure 'false' is false.
       
  1045 	if ( is_string( $value ) ) {
       
  1046 		$value = strtolower( $value );
       
  1047 		if ( in_array( $value, array( 'false', '0' ), true ) ) {
       
  1048 			$value = false;
       
  1049 		}
       
  1050 	}
       
  1051 
       
  1052 	// Everything else will map nicely to boolean.
       
  1053 	return (bool) $value;
       
  1054 }
       
  1055 
       
  1056 /**
       
  1057  * Determines if a given value is boolean-like.
       
  1058  *
       
  1059  * @since 4.7.0
       
  1060  *
       
  1061  * @param bool|string $maybe_bool The value being evaluated.
       
  1062  * @return boolean True if a boolean, otherwise false.
       
  1063  */
       
  1064 function rest_is_boolean( $maybe_bool ) {
       
  1065 	if ( is_bool( $maybe_bool ) ) {
       
  1066 		return true;
       
  1067 	}
       
  1068 
       
  1069 	if ( is_string( $maybe_bool ) ) {
       
  1070 		$maybe_bool = strtolower( $maybe_bool );
       
  1071 
       
  1072 		$valid_boolean_values = array(
       
  1073 			'false',
       
  1074 			'true',
       
  1075 			'0',
       
  1076 			'1',
       
  1077 		);
       
  1078 
       
  1079 		return in_array( $maybe_bool, $valid_boolean_values, true );
       
  1080 	}
       
  1081 
       
  1082 	if ( is_int( $maybe_bool ) ) {
       
  1083 		return in_array( $maybe_bool, array( 0, 1 ), true );
       
  1084 	}
       
  1085 
       
  1086 	return false;
       
  1087 }
       
  1088 
       
  1089 /**
       
  1090  * Retrieves the avatar urls in various sizes based on a given email address.
       
  1091  *
       
  1092  * @since 4.7.0
       
  1093  *
       
  1094  * @see get_avatar_url()
  1037  * @see get_avatar_url()
  1095  *
  1038  *
  1096  * @param string $email Email address.
  1039  * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
  1097  * @return array $urls Gravatar url for each size.
  1040  *                           user email, WP_User object, WP_Post object, or WP_Comment object.
  1098  */
  1041  * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
  1099 function rest_get_avatar_urls( $email ) {
  1042  */
       
  1043 function rest_get_avatar_urls( $id_or_email ) {
  1100 	$avatar_sizes = rest_get_avatar_sizes();
  1044 	$avatar_sizes = rest_get_avatar_sizes();
  1101 
  1045 
  1102 	$urls = array();
  1046 	$urls = array();
  1103 	foreach ( $avatar_sizes as $size ) {
  1047 	foreach ( $avatar_sizes as $size ) {
  1104 		$urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
  1048 		$urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
  1105 	}
  1049 	}
  1106 
  1050 
  1107 	return $urls;
  1051 	return $urls;
  1108 }
  1052 }
  1109 
  1053 
  1110 /**
  1054 /**
  1111  * Retrieves the pixel sizes for avatars.
  1055  * Retrieves the pixel sizes for avatars.
  1112  *
  1056  *
  1113  * @since 4.7.0
  1057  * @since 4.7.0
  1114  *
  1058  *
  1115  * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
  1059  * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
  1116  */
  1060  */
  1117 function rest_get_avatar_sizes() {
  1061 function rest_get_avatar_sizes() {
  1118 	/**
  1062 	/**
  1119 	 * Filters the REST avatar sizes.
  1063 	 * Filters the REST avatar sizes.
  1120 	 *
  1064 	 *
  1121 	 * Use this filter to adjust the array of sizes returned by the
  1065 	 * Use this filter to adjust the array of sizes returned by the
  1122 	 * `rest_get_avatar_sizes` function.
  1066 	 * `rest_get_avatar_sizes` function.
  1123 	 *
  1067 	 *
  1124 	 * @since 4.4.0
  1068 	 * @since 4.4.0
  1125 	 *
  1069 	 *
  1126 	 * @param array $sizes An array of int values that are the pixel sizes for avatars.
  1070 	 * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
  1127 	 *                     Default `[ 24, 48, 96 ]`.
  1071 	 *                     Default `[ 24, 48, 96 ]`.
  1128 	 */
  1072 	 */
  1129 	return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
  1073 	return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
  1130 }
  1074 }
  1131 
  1075 
  1132 /**
  1076 /**
       
  1077  * Parses an RFC3339 time into a Unix timestamp.
       
  1078  *
       
  1079  * @since 4.4.0
       
  1080  *
       
  1081  * @param string $date      RFC3339 timestamp.
       
  1082  * @param bool   $force_utc Optional. Whether to force UTC timezone instead of using
       
  1083  *                          the timestamp's timezone. Default false.
       
  1084  * @return int Unix timestamp.
       
  1085  */
       
  1086 function rest_parse_date( $date, $force_utc = false ) {
       
  1087 	if ( $force_utc ) {
       
  1088 		$date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
       
  1089 	}
       
  1090 
       
  1091 	$regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
       
  1092 
       
  1093 	if ( ! preg_match( $regex, $date, $matches ) ) {
       
  1094 		return false;
       
  1095 	}
       
  1096 
       
  1097 	return strtotime( $date );
       
  1098 }
       
  1099 
       
  1100 /**
       
  1101  * Parses a 3 or 6 digit hex color (with #).
       
  1102  *
       
  1103  * @since 5.4.0
       
  1104  *
       
  1105  * @param string $color 3 or 6 digit hex color (with #).
       
  1106  * @return string|false
       
  1107  */
       
  1108 function rest_parse_hex_color( $color ) {
       
  1109 	$regex = '|^#([A-Fa-f0-9]{3}){1,2}$|';
       
  1110 	if ( ! preg_match( $regex, $color, $matches ) ) {
       
  1111 		return false;
       
  1112 	}
       
  1113 
       
  1114 	return $color;
       
  1115 }
       
  1116 
       
  1117 /**
       
  1118  * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
       
  1119  *
       
  1120  * @since 4.4.0
       
  1121  *
       
  1122  * @see rest_parse_date()
       
  1123  *
       
  1124  * @param string $date   RFC3339 timestamp.
       
  1125  * @param bool   $is_utc Whether the provided date should be interpreted as UTC. Default false.
       
  1126  * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
       
  1127  *                    null on failure.
       
  1128  */
       
  1129 function rest_get_date_with_gmt( $date, $is_utc = false ) {
       
  1130 	/*
       
  1131 	 * Whether or not the original date actually has a timezone string
       
  1132 	 * changes the way we need to do timezone conversion.
       
  1133 	 * Store this info before parsing the date, and use it later.
       
  1134 	 */
       
  1135 	$has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
       
  1136 
       
  1137 	$date = rest_parse_date( $date );
       
  1138 
       
  1139 	if ( empty( $date ) ) {
       
  1140 		return null;
       
  1141 	}
       
  1142 
       
  1143 	/*
       
  1144 	 * At this point $date could either be a local date (if we were passed
       
  1145 	 * a *local* date without a timezone offset) or a UTC date (otherwise).
       
  1146 	 * Timezone conversion needs to be handled differently between these two cases.
       
  1147 	 */
       
  1148 	if ( ! $is_utc && ! $has_timezone ) {
       
  1149 		$local = gmdate( 'Y-m-d H:i:s', $date );
       
  1150 		$utc   = get_gmt_from_date( $local );
       
  1151 	} else {
       
  1152 		$utc   = gmdate( 'Y-m-d H:i:s', $date );
       
  1153 		$local = get_date_from_gmt( $utc );
       
  1154 	}
       
  1155 
       
  1156 	return array( $local, $utc );
       
  1157 }
       
  1158 
       
  1159 /**
       
  1160  * Returns a contextual HTTP error code for authorization failure.
       
  1161  *
       
  1162  * @since 4.7.0
       
  1163  *
       
  1164  * @return integer 401 if the user is not logged in, 403 if the user is logged in.
       
  1165  */
       
  1166 function rest_authorization_required_code() {
       
  1167 	return is_user_logged_in() ? 403 : 401;
       
  1168 }
       
  1169 
       
  1170 /**
       
  1171  * Validate a request argument based on details registered to the route.
       
  1172  *
       
  1173  * @since 4.7.0
       
  1174  *
       
  1175  * @param mixed           $value
       
  1176  * @param WP_REST_Request $request
       
  1177  * @param string          $param
       
  1178  * @return true|WP_Error
       
  1179  */
       
  1180 function rest_validate_request_arg( $value, $request, $param ) {
       
  1181 	$attributes = $request->get_attributes();
       
  1182 	if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
       
  1183 		return true;
       
  1184 	}
       
  1185 	$args = $attributes['args'][ $param ];
       
  1186 
       
  1187 	return rest_validate_value_from_schema( $value, $args, $param );
       
  1188 }
       
  1189 
       
  1190 /**
       
  1191  * Sanitize a request argument based on details registered to the route.
       
  1192  *
       
  1193  * @since 4.7.0
       
  1194  *
       
  1195  * @param mixed           $value
       
  1196  * @param WP_REST_Request $request
       
  1197  * @param string          $param
       
  1198  * @return mixed
       
  1199  */
       
  1200 function rest_sanitize_request_arg( $value, $request, $param ) {
       
  1201 	$attributes = $request->get_attributes();
       
  1202 	if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
       
  1203 		return $value;
       
  1204 	}
       
  1205 	$args = $attributes['args'][ $param ];
       
  1206 
       
  1207 	return rest_sanitize_value_from_schema( $value, $args, $param );
       
  1208 }
       
  1209 
       
  1210 /**
       
  1211  * Parse a request argument based on details registered to the route.
       
  1212  *
       
  1213  * Runs a validation check and sanitizes the value, primarily to be used via
       
  1214  * the `sanitize_callback` arguments in the endpoint args registration.
       
  1215  *
       
  1216  * @since 4.7.0
       
  1217  *
       
  1218  * @param mixed           $value
       
  1219  * @param WP_REST_Request $request
       
  1220  * @param string          $param
       
  1221  * @return mixed
       
  1222  */
       
  1223 function rest_parse_request_arg( $value, $request, $param ) {
       
  1224 	$is_valid = rest_validate_request_arg( $value, $request, $param );
       
  1225 
       
  1226 	if ( is_wp_error( $is_valid ) ) {
       
  1227 		return $is_valid;
       
  1228 	}
       
  1229 
       
  1230 	$value = rest_sanitize_request_arg( $value, $request, $param );
       
  1231 
       
  1232 	return $value;
       
  1233 }
       
  1234 
       
  1235 /**
       
  1236  * Determines if an IP address is valid.
       
  1237  *
       
  1238  * Handles both IPv4 and IPv6 addresses.
       
  1239  *
       
  1240  * @since 4.7.0
       
  1241  *
       
  1242  * @param string $ip IP address.
       
  1243  * @return string|false The valid IP address, otherwise false.
       
  1244  */
       
  1245 function rest_is_ip_address( $ip ) {
       
  1246 	$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]?)$/';
       
  1247 
       
  1248 	if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
       
  1249 		return false;
       
  1250 	}
       
  1251 
       
  1252 	return $ip;
       
  1253 }
       
  1254 
       
  1255 /**
       
  1256  * Changes a boolean-like value into the proper boolean value.
       
  1257  *
       
  1258  * @since 4.7.0
       
  1259  *
       
  1260  * @param bool|string|int $value The value being evaluated.
       
  1261  * @return boolean Returns the proper associated boolean value.
       
  1262  */
       
  1263 function rest_sanitize_boolean( $value ) {
       
  1264 	// String values are translated to `true`; make sure 'false' is false.
       
  1265 	if ( is_string( $value ) ) {
       
  1266 		$value = strtolower( $value );
       
  1267 		if ( in_array( $value, array( 'false', '0' ), true ) ) {
       
  1268 			$value = false;
       
  1269 		}
       
  1270 	}
       
  1271 
       
  1272 	// Everything else will map nicely to boolean.
       
  1273 	return (bool) $value;
       
  1274 }
       
  1275 
       
  1276 /**
       
  1277  * Determines if a given value is boolean-like.
       
  1278  *
       
  1279  * @since 4.7.0
       
  1280  *
       
  1281  * @param bool|string $maybe_bool The value being evaluated.
       
  1282  * @return boolean True if a boolean, otherwise false.
       
  1283  */
       
  1284 function rest_is_boolean( $maybe_bool ) {
       
  1285 	if ( is_bool( $maybe_bool ) ) {
       
  1286 		return true;
       
  1287 	}
       
  1288 
       
  1289 	if ( is_string( $maybe_bool ) ) {
       
  1290 		$maybe_bool = strtolower( $maybe_bool );
       
  1291 
       
  1292 		$valid_boolean_values = array(
       
  1293 			'false',
       
  1294 			'true',
       
  1295 			'0',
       
  1296 			'1',
       
  1297 		);
       
  1298 
       
  1299 		return in_array( $maybe_bool, $valid_boolean_values, true );
       
  1300 	}
       
  1301 
       
  1302 	if ( is_int( $maybe_bool ) ) {
       
  1303 		return in_array( $maybe_bool, array( 0, 1 ), true );
       
  1304 	}
       
  1305 
       
  1306 	return false;
       
  1307 }
       
  1308 
       
  1309 /**
       
  1310  * Determines if a given value is integer-like.
       
  1311  *
       
  1312  * @since 5.5.0
       
  1313  *
       
  1314  * @param mixed $maybe_integer The value being evaluated.
       
  1315  * @return bool True if an integer, otherwise false.
       
  1316  */
       
  1317 function rest_is_integer( $maybe_integer ) {
       
  1318 	return is_numeric( $maybe_integer ) && round( floatval( $maybe_integer ) ) === floatval( $maybe_integer );
       
  1319 }
       
  1320 
       
  1321 /**
       
  1322  * Determines if a given value is array-like.
       
  1323  *
       
  1324  * @since 5.5.0
       
  1325  *
       
  1326  * @param mixed $maybe_array The value being evaluated.
       
  1327  * @return bool
       
  1328  */
       
  1329 function rest_is_array( $maybe_array ) {
       
  1330 	if ( is_scalar( $maybe_array ) ) {
       
  1331 		$maybe_array = wp_parse_list( $maybe_array );
       
  1332 	}
       
  1333 
       
  1334 	return wp_is_numeric_array( $maybe_array );
       
  1335 }
       
  1336 
       
  1337 /**
       
  1338  * Converts an array-like value to an array.
       
  1339  *
       
  1340  * @since 5.5.0
       
  1341  *
       
  1342  * @param mixed $maybe_array The value being evaluated.
       
  1343  * @return array Returns the array extracted from the value.
       
  1344  */
       
  1345 function rest_sanitize_array( $maybe_array ) {
       
  1346 	if ( is_scalar( $maybe_array ) ) {
       
  1347 		return wp_parse_list( $maybe_array );
       
  1348 	}
       
  1349 
       
  1350 	if ( ! is_array( $maybe_array ) ) {
       
  1351 		return array();
       
  1352 	}
       
  1353 
       
  1354 	// Normalize to numeric array so nothing unexpected is in the keys.
       
  1355 	return array_values( $maybe_array );
       
  1356 }
       
  1357 
       
  1358 /**
       
  1359  * Determines if a given value is object-like.
       
  1360  *
       
  1361  * @since 5.5.0
       
  1362  *
       
  1363  * @param mixed $maybe_object The value being evaluated.
       
  1364  * @return bool True if object like, otherwise false.
       
  1365  */
       
  1366 function rest_is_object( $maybe_object ) {
       
  1367 	if ( '' === $maybe_object ) {
       
  1368 		return true;
       
  1369 	}
       
  1370 
       
  1371 	if ( $maybe_object instanceof stdClass ) {
       
  1372 		return true;
       
  1373 	}
       
  1374 
       
  1375 	if ( $maybe_object instanceof JsonSerializable ) {
       
  1376 		$maybe_object = $maybe_object->jsonSerialize();
       
  1377 	}
       
  1378 
       
  1379 	return is_array( $maybe_object );
       
  1380 }
       
  1381 
       
  1382 /**
       
  1383  * Converts an object-like value to an object.
       
  1384  *
       
  1385  * @since 5.5.0
       
  1386  *
       
  1387  * @param mixed $maybe_object The value being evaluated.
       
  1388  * @return array Returns the object extracted from the value.
       
  1389  */
       
  1390 function rest_sanitize_object( $maybe_object ) {
       
  1391 	if ( '' === $maybe_object ) {
       
  1392 		return array();
       
  1393 	}
       
  1394 
       
  1395 	if ( $maybe_object instanceof stdClass ) {
       
  1396 		return (array) $maybe_object;
       
  1397 	}
       
  1398 
       
  1399 	if ( $maybe_object instanceof JsonSerializable ) {
       
  1400 		$maybe_object = $maybe_object->jsonSerialize();
       
  1401 	}
       
  1402 
       
  1403 	if ( ! is_array( $maybe_object ) ) {
       
  1404 		return array();
       
  1405 	}
       
  1406 
       
  1407 	return $maybe_object;
       
  1408 }
       
  1409 
       
  1410 /**
       
  1411  * Gets the best type for a value.
       
  1412  *
       
  1413  * @since 5.5.0
       
  1414  *
       
  1415  * @param mixed $value The value to check.
       
  1416  * @param array $types The list of possible types.
       
  1417  * @return string The best matching type, an empty string if no types match.
       
  1418  */
       
  1419 function rest_get_best_type_for_value( $value, $types ) {
       
  1420 	static $checks = array(
       
  1421 		'array'   => 'rest_is_array',
       
  1422 		'object'  => 'rest_is_object',
       
  1423 		'integer' => 'rest_is_integer',
       
  1424 		'number'  => 'is_numeric',
       
  1425 		'boolean' => 'rest_is_boolean',
       
  1426 		'string'  => 'is_string',
       
  1427 		'null'    => 'is_null',
       
  1428 	);
       
  1429 
       
  1430 	// Both arrays and objects allow empty strings to be converted to their types.
       
  1431 	// But the best answer for this type is a string.
       
  1432 	if ( '' === $value && in_array( 'string', $types, true ) ) {
       
  1433 		return 'string';
       
  1434 	}
       
  1435 
       
  1436 	foreach ( $types as $type ) {
       
  1437 		if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
       
  1438 			return $type;
       
  1439 		}
       
  1440 	}
       
  1441 
       
  1442 	return '';
       
  1443 }
       
  1444 
       
  1445 /**
       
  1446  * Handles getting the best type for a multi-type schema.
       
  1447  *
       
  1448  * This is a wrapper for {@see rest_get_best_type_for_value()} that handles
       
  1449  * backward compatibility for schemas that use invalid types.
       
  1450  *
       
  1451  * @since 5.5.0
       
  1452  *
       
  1453  * @param mixed  $value The value to check.
       
  1454  * @param array  $args  The schema array to use.
       
  1455  * @param string $param The parameter name, used in error messages.
       
  1456  * @return string
       
  1457  */
       
  1458 function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
       
  1459 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
       
  1460 	$invalid_types = array_diff( $args['type'], $allowed_types );
       
  1461 
       
  1462 	if ( $invalid_types ) {
       
  1463 		_doing_it_wrong(
       
  1464 			__FUNCTION__,
       
  1465 			/* translators: 1. Parameter. 2. List of allowed types. */
       
  1466 			wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
       
  1467 			'5.5.0'
       
  1468 		);
       
  1469 	}
       
  1470 
       
  1471 	$best_type = rest_get_best_type_for_value( $value, $args['type'] );
       
  1472 
       
  1473 	if ( ! $best_type ) {
       
  1474 		if ( ! $invalid_types ) {
       
  1475 			return '';
       
  1476 		}
       
  1477 
       
  1478 		// Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
       
  1479 		$best_type = reset( $invalid_types );
       
  1480 	}
       
  1481 
       
  1482 	return $best_type;
       
  1483 }
       
  1484 
       
  1485 /**
       
  1486  * Checks if an array is made up of unique items.
       
  1487  *
       
  1488  * @since 5.5.0
       
  1489  *
       
  1490  * @param array $array The array to check.
       
  1491  * @return bool True if the array contains unique items, false otherwise.
       
  1492  */
       
  1493 function rest_validate_array_contains_unique_items( $array ) {
       
  1494 	$seen = array();
       
  1495 
       
  1496 	foreach ( $array as $item ) {
       
  1497 		$stabilized = rest_stabilize_value( $item );
       
  1498 		$key        = serialize( $stabilized );
       
  1499 
       
  1500 		if ( ! isset( $seen[ $key ] ) ) {
       
  1501 			$seen[ $key ] = true;
       
  1502 
       
  1503 			continue;
       
  1504 		}
       
  1505 
       
  1506 		return false;
       
  1507 	}
       
  1508 
       
  1509 	return true;
       
  1510 }
       
  1511 
       
  1512 /**
       
  1513  * Stabilizes a value following JSON Schema semantics.
       
  1514  *
       
  1515  * For lists, order is preserved. For objects, properties are reordered alphabetically.
       
  1516  *
       
  1517  * @since 5.5.0
       
  1518  *
       
  1519  * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays.
       
  1520  * @return mixed The stabilized value.
       
  1521  */
       
  1522 function rest_stabilize_value( $value ) {
       
  1523 	if ( is_scalar( $value ) || is_null( $value ) ) {
       
  1524 		return $value;
       
  1525 	}
       
  1526 
       
  1527 	if ( is_object( $value ) ) {
       
  1528 		_doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' );
       
  1529 
       
  1530 		return $value;
       
  1531 	}
       
  1532 
       
  1533 	ksort( $value );
       
  1534 
       
  1535 	foreach ( $value as $k => $v ) {
       
  1536 		$value[ $k ] = rest_stabilize_value( $v );
       
  1537 	}
       
  1538 
       
  1539 	return $value;
       
  1540 }
       
  1541 
       
  1542 /**
  1133  * Validate a value based on a schema.
  1543  * Validate a value based on a schema.
  1134  *
  1544  *
  1135  * @since 4.7.0
  1545  * @since 4.7.0
       
  1546  * @since 4.9.0 Support the "object" type.
       
  1547  * @since 5.2.0 Support validating "additionalProperties" against a schema.
       
  1548  * @since 5.3.0 Support multiple types.
       
  1549  * @since 5.4.0 Convert an empty string to an empty object.
       
  1550  * @since 5.5.0 Add the "uuid" and "hex-color" formats.
       
  1551  *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
       
  1552  *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
       
  1553  *              Validate required properties.
  1136  *
  1554  *
  1137  * @param mixed  $value The value to validate.
  1555  * @param mixed  $value The value to validate.
  1138  * @param array  $args  Schema array to use for validation.
  1556  * @param array  $args  Schema array to use for validation.
  1139  * @param string $param The parameter name, used in error messages.
  1557  * @param string $param The parameter name, used in error messages.
  1140  * @return true|WP_Error
  1558  * @return true|WP_Error
  1141  */
  1559  */
  1142 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
  1560 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
       
  1561 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
       
  1562 
       
  1563 	if ( ! isset( $args['type'] ) ) {
       
  1564 		/* translators: 1. Parameter */
       
  1565 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
       
  1566 	}
       
  1567 
       
  1568 	if ( is_array( $args['type'] ) ) {
       
  1569 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
       
  1570 
       
  1571 		if ( ! $best_type ) {
       
  1572 			/* translators: 1: Parameter, 2: List of types. */
       
  1573 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
       
  1574 		}
       
  1575 
       
  1576 		$args['type'] = $best_type;
       
  1577 	}
       
  1578 
       
  1579 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
       
  1580 		_doing_it_wrong(
       
  1581 			__FUNCTION__,
       
  1582 			/* translators: 1. Parameter 2. The list of allowed types. */
       
  1583 			wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
       
  1584 			'5.5.0'
       
  1585 		);
       
  1586 	}
       
  1587 
  1143 	if ( 'array' === $args['type'] ) {
  1588 	if ( 'array' === $args['type'] ) {
  1144 		if ( ! is_null( $value ) ) {
  1589 		if ( ! rest_is_array( $value ) ) {
  1145 			$value = wp_parse_list( $value );
  1590 			/* translators: 1: Parameter, 2: Type name. */
  1146 		}
       
  1147 		if ( ! wp_is_numeric_array( $value ) ) {
       
  1148 			/* translators: 1: parameter, 2: type name */
       
  1149 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  1591 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  1150 		}
  1592 		}
  1151 		foreach ( $value as $index => $v ) {
  1593 
  1152 			$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  1594 		$value = rest_sanitize_array( $value );
  1153 			if ( is_wp_error( $is_valid ) ) {
  1595 
  1154 				return $is_valid;
  1596 		if ( isset( $args['items'] ) ) {
       
  1597 			foreach ( $value as $index => $v ) {
       
  1598 				$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
       
  1599 				if ( is_wp_error( $is_valid ) ) {
       
  1600 					return $is_valid;
       
  1601 				}
  1155 			}
  1602 			}
  1156 		}
  1603 		}
       
  1604 
       
  1605 		if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
       
  1606 			/* translators: 1: Parameter, 2: Number. */
       
  1607 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) );
       
  1608 		}
       
  1609 
       
  1610 		if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
       
  1611 			/* translators: 1: Parameter, 2: Number. */
       
  1612 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) );
       
  1613 		}
       
  1614 
       
  1615 		if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
       
  1616 			/* translators: 1: Parameter */
       
  1617 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
       
  1618 		}
  1157 	}
  1619 	}
  1158 
  1620 
  1159 	if ( 'object' === $args['type'] ) {
  1621 	if ( 'object' === $args['type'] ) {
  1160 		if ( $value instanceof stdClass ) {
  1622 		if ( ! rest_is_object( $value ) ) {
  1161 			$value = (array) $value;
  1623 			/* translators: 1: Parameter, 2: Type name. */
  1162 		}
       
  1163 		if ( ! is_array( $value ) ) {
       
  1164 			/* translators: 1: parameter, 2: type name */
       
  1165 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
  1624 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
       
  1625 		}
       
  1626 
       
  1627 		$value = rest_sanitize_object( $value );
       
  1628 
       
  1629 		if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
       
  1630 			foreach ( $args['required'] as $name ) {
       
  1631 				if ( ! array_key_exists( $name, $value ) ) {
       
  1632 					/* translators: 1: Property of an object, 2: Parameter. */
       
  1633 					return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
       
  1634 				}
       
  1635 			}
       
  1636 		} elseif ( isset( $args['properties'] ) ) { // schema version 3
       
  1637 			foreach ( $args['properties'] as $name => $property ) {
       
  1638 				if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
       
  1639 					/* translators: 1: Property of an object, 2: Parameter. */
       
  1640 					return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
       
  1641 				}
       
  1642 			}
  1166 		}
  1643 		}
  1167 
  1644 
  1168 		foreach ( $value as $property => $v ) {
  1645 		foreach ( $value as $property => $v ) {
  1169 			if ( isset( $args['properties'][ $property ] ) ) {
  1646 			if ( isset( $args['properties'][ $property ] ) ) {
  1170 				$is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  1647 				$is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  1171 				if ( is_wp_error( $is_valid ) ) {
  1648 				if ( is_wp_error( $is_valid ) ) {
  1172 					return $is_valid;
  1649 					return $is_valid;
  1173 				}
  1650 				}
  1174 			} elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
  1651 			} elseif ( isset( $args['additionalProperties'] ) ) {
  1175 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
  1652 				if ( false === $args['additionalProperties'] ) {
       
  1653 					/* translators: %s: Property of an object. */
       
  1654 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
       
  1655 				}
       
  1656 
       
  1657 				if ( is_array( $args['additionalProperties'] ) ) {
       
  1658 					$is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
       
  1659 					if ( is_wp_error( $is_valid ) ) {
       
  1660 						return $is_valid;
       
  1661 					}
       
  1662 				}
  1176 			}
  1663 			}
  1177 		}
  1664 		}
       
  1665 	}
       
  1666 
       
  1667 	if ( 'null' === $args['type'] ) {
       
  1668 		if ( null !== $value ) {
       
  1669 			/* translators: 1: Parameter, 2: Type name. */
       
  1670 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
       
  1671 		}
       
  1672 
       
  1673 		return true;
  1178 	}
  1674 	}
  1179 
  1675 
  1180 	if ( ! empty( $args['enum'] ) ) {
  1676 	if ( ! empty( $args['enum'] ) ) {
  1181 		if ( ! in_array( $value, $args['enum'], true ) ) {
  1677 		if ( ! in_array( $value, $args['enum'], true ) ) {
  1182 			/* translators: 1: parameter, 2: list of valid values */
  1678 			/* translators: 1: Parameter, 2: List of valid values. */
  1183 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
  1679 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
  1184 		}
  1680 		}
  1185 	}
  1681 	}
  1186 
  1682 
  1187 	if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) {
  1683 	if ( in_array( $args['type'], array( 'integer', 'number' ), true ) && ! is_numeric( $value ) ) {
  1188 		/* translators: 1: parameter, 2: type name */
  1684 		/* translators: 1: Parameter, 2: Type name. */
  1189 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
  1685 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
  1190 	}
  1686 	}
  1191 
  1687 
  1192 	if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
  1688 	if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
  1193 		/* translators: 1: parameter, 2: type name */
  1689 		/* translators: 1: Parameter, 2: Type name. */
  1194 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
  1690 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
  1195 	}
  1691 	}
  1196 
  1692 
  1197 	if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
  1693 	if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
  1198 		/* translators: 1: parameter, 2: type name */
  1694 		/* translators: 1: Parameter, 2: Type name. */
  1199 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) );
  1695 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
  1200 	}
  1696 	}
  1201 
  1697 
  1202 	if ( 'string' === $args['type'] && ! is_string( $value ) ) {
  1698 	if ( 'string' === $args['type'] ) {
  1203 		/* translators: 1: parameter, 2: type name */
  1699 		if ( ! is_string( $value ) ) {
  1204 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
  1700 			/* translators: 1: Parameter, 2: Type name. */
  1205 	}
  1701 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
  1206 
  1702 		}
  1207 	if ( isset( $args['format'] ) ) {
  1703 
       
  1704 		if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
       
  1705 			return new WP_Error(
       
  1706 				'rest_invalid_param',
       
  1707 				sprintf(
       
  1708 					/* translators: 1: Parameter, 2: Number of characters. */
       
  1709 					_n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ),
       
  1710 					$param,
       
  1711 					number_format_i18n( $args['minLength'] )
       
  1712 				)
       
  1713 			);
       
  1714 		}
       
  1715 
       
  1716 		if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
       
  1717 			return new WP_Error(
       
  1718 				'rest_invalid_param',
       
  1719 				sprintf(
       
  1720 					/* translators: 1: Parameter, 2: Number of characters. */
       
  1721 					_n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ),
       
  1722 					$param,
       
  1723 					number_format_i18n( $args['maxLength'] )
       
  1724 				)
       
  1725 			);
       
  1726 		}
       
  1727 
       
  1728 		if ( isset( $args['pattern'] ) ) {
       
  1729 			$pattern = str_replace( '#', '\\#', $args['pattern'] );
       
  1730 			if ( ! preg_match( '#' . $pattern . '#u', $value ) ) {
       
  1731 				/* translators: 1: Parameter, 2: Pattern. */
       
  1732 				return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
       
  1733 			}
       
  1734 		}
       
  1735 	}
       
  1736 
       
  1737 	// The "format" keyword should only be applied to strings. However, for backward compatibility,
       
  1738 	// we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
       
  1739 	if ( isset( $args['format'] )
       
  1740 		&& ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
       
  1741 	) {
  1208 		switch ( $args['format'] ) {
  1742 		switch ( $args['format'] ) {
       
  1743 			case 'hex-color':
       
  1744 				if ( ! rest_parse_hex_color( $value ) ) {
       
  1745 					return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
       
  1746 				}
       
  1747 				break;
       
  1748 
  1209 			case 'date-time':
  1749 			case 'date-time':
  1210 				if ( ! rest_parse_date( $value ) ) {
  1750 				if ( ! rest_parse_date( $value ) ) {
  1211 					return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
  1751 					return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
  1212 				}
  1752 				}
  1213 				break;
  1753 				break;
  1217 					return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
  1757 					return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
  1218 				}
  1758 				}
  1219 				break;
  1759 				break;
  1220 			case 'ip':
  1760 			case 'ip':
  1221 				if ( ! rest_is_ip_address( $value ) ) {
  1761 				if ( ! rest_is_ip_address( $value ) ) {
  1222 					/* translators: %s: IP address */
  1762 					/* translators: %s: IP address. */
  1223 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
  1763 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
       
  1764 				}
       
  1765 				break;
       
  1766 			case 'uuid':
       
  1767 				if ( ! wp_is_uuid( $value ) ) {
       
  1768 					/* translators: %s is the name of a JSON field expecting a valid uuid. */
       
  1769 					return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
  1224 				}
  1770 				}
  1225 				break;
  1771 				break;
  1226 		}
  1772 		}
  1227 	}
  1773 	}
  1228 
  1774 
  1229 	if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
  1775 	if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
  1230 		if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
  1776 		if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
  1231 			if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
  1777 			if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
  1232 				/* translators: 1: parameter, 2: minimum number */
  1778 				/* translators: 1: Parameter, 2: Minimum number. */
  1233 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
  1779 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
  1234 			} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
  1780 			} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
  1235 				/* translators: 1: parameter, 2: minimum number */
  1781 				/* translators: 1: Parameter, 2: Minimum number. */
  1236 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
  1782 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
  1237 			}
  1783 			}
  1238 		} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
  1784 		} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
  1239 			if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
  1785 			if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
  1240 				/* translators: 1: parameter, 2: maximum number */
  1786 				/* translators: 1: Parameter, 2: Maximum number. */
  1241 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
  1787 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
  1242 			} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
  1788 			} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
  1243 				/* translators: 1: parameter, 2: maximum number */
  1789 				/* translators: 1: Parameter, 2: Maximum number. */
  1244 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
  1790 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
  1245 			}
  1791 			}
  1246 		} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
  1792 		} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
  1247 			if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1793 			if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1248 				if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
  1794 				if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
  1249 					/* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1795 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  1250 					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'] ) );
  1796 					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'] ) );
  1251 				}
  1797 				}
  1252 			} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1798 			} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1253 				if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
  1799 				if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
  1254 					/* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1800 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  1255 					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'] ) );
  1801 					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'] ) );
  1256 				}
  1802 				}
  1257 			} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1803 			} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1258 				if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
  1804 				if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
  1259 					/* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1805 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  1260 					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'] ) );
  1806 					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'] ) );
  1261 				}
  1807 				}
  1262 			} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1808 			} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1263 				if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
  1809 				if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
  1264 					/* translators: 1: parameter, 2: minimum number, 3: maximum number */
  1810 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  1265 					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'] ) );
  1811 					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'] ) );
  1266 				}
  1812 				}
  1267 			}
  1813 			}
  1268 		}
  1814 		}
  1269 	}
  1815 	}
  1273 
  1819 
  1274 /**
  1820 /**
  1275  * Sanitize a value based on a schema.
  1821  * Sanitize a value based on a schema.
  1276  *
  1822  *
  1277  * @since 4.7.0
  1823  * @since 4.7.0
  1278  *
  1824  * @since 5.5.0 Added the `$param` parameter.
  1279  * @param mixed $value The value to sanitize.
  1825  *
  1280  * @param array $args  Schema array to use for sanitization.
  1826  * @param mixed  $value The value to sanitize.
  1281  * @return true|WP_Error
  1827  * @param array  $args  Schema array to use for sanitization.
  1282  */
  1828  * @param string $param The parameter name, used in error messages.
  1283 function rest_sanitize_value_from_schema( $value, $args ) {
  1829  * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
       
  1830  */
       
  1831 function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
       
  1832 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
       
  1833 
       
  1834 	if ( ! isset( $args['type'] ) ) {
       
  1835 		/* translators: 1. Parameter */
       
  1836 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
       
  1837 	}
       
  1838 
       
  1839 	if ( is_array( $args['type'] ) ) {
       
  1840 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
       
  1841 
       
  1842 		if ( ! $best_type ) {
       
  1843 			return null;
       
  1844 		}
       
  1845 
       
  1846 		$args['type'] = $best_type;
       
  1847 	}
       
  1848 
       
  1849 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
       
  1850 		_doing_it_wrong(
       
  1851 			__FUNCTION__,
       
  1852 			/* translators: 1. Parameter. 2. The list of allowed types. */
       
  1853 			wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
       
  1854 			'5.5.0'
       
  1855 		);
       
  1856 	}
       
  1857 
  1284 	if ( 'array' === $args['type'] ) {
  1858 	if ( 'array' === $args['type'] ) {
  1285 		if ( empty( $args['items'] ) ) {
  1859 		$value = rest_sanitize_array( $value );
  1286 			return (array) $value;
  1860 
  1287 		}
  1861 		if ( ! empty( $args['items'] ) ) {
  1288 		$value = wp_parse_list( $value );
  1862 			foreach ( $value as $index => $v ) {
  1289 		foreach ( $value as $index => $v ) {
  1863 				$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  1290 			$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
  1864 			}
  1291 		}
  1865 		}
  1292 		// Normalize to numeric array so nothing unexpected
  1866 
  1293 		// is in the keys.
  1867 		if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
  1294 		$value = array_values( $value );
  1868 			/* translators: 1: Parameter */
       
  1869 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
       
  1870 		}
       
  1871 
  1295 		return $value;
  1872 		return $value;
  1296 	}
  1873 	}
  1297 
  1874 
  1298 	if ( 'object' === $args['type'] ) {
  1875 	if ( 'object' === $args['type'] ) {
  1299 		if ( $value instanceof stdClass ) {
  1876 		$value = rest_sanitize_object( $value );
  1300 			$value = (array) $value;
       
  1301 		}
       
  1302 		if ( ! is_array( $value ) ) {
       
  1303 			return array();
       
  1304 		}
       
  1305 
  1877 
  1306 		foreach ( $value as $property => $v ) {
  1878 		foreach ( $value as $property => $v ) {
  1307 			if ( isset( $args['properties'][ $property ] ) ) {
  1879 			if ( isset( $args['properties'][ $property ] ) ) {
  1308 				$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
  1880 				$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  1309 			} elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
  1881 			} elseif ( isset( $args['additionalProperties'] ) ) {
  1310 				unset( $value[ $property ] );
  1882 				if ( false === $args['additionalProperties'] ) {
       
  1883 					unset( $value[ $property ] );
       
  1884 				} elseif ( is_array( $args['additionalProperties'] ) ) {
       
  1885 					$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
       
  1886 				}
  1311 			}
  1887 			}
  1312 		}
  1888 		}
  1313 
  1889 
  1314 		return $value;
  1890 		return $value;
       
  1891 	}
       
  1892 
       
  1893 	if ( 'null' === $args['type'] ) {
       
  1894 		return null;
  1315 	}
  1895 	}
  1316 
  1896 
  1317 	if ( 'integer' === $args['type'] ) {
  1897 	if ( 'integer' === $args['type'] ) {
  1318 		return (int) $value;
  1898 		return (int) $value;
  1319 	}
  1899 	}
  1324 
  1904 
  1325 	if ( 'boolean' === $args['type'] ) {
  1905 	if ( 'boolean' === $args['type'] ) {
  1326 		return rest_sanitize_boolean( $value );
  1906 		return rest_sanitize_boolean( $value );
  1327 	}
  1907 	}
  1328 
  1908 
  1329 	if ( isset( $args['format'] ) ) {
  1909 	// This behavior matches rest_validate_value_from_schema().
       
  1910 	if ( isset( $args['format'] )
       
  1911 		&& ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
       
  1912 	) {
  1330 		switch ( $args['format'] ) {
  1913 		switch ( $args['format'] ) {
       
  1914 			case 'hex-color':
       
  1915 				return (string) sanitize_hex_color( $value );
       
  1916 
  1331 			case 'date-time':
  1917 			case 'date-time':
  1332 				return sanitize_text_field( $value );
  1918 				return sanitize_text_field( $value );
  1333 
  1919 
  1334 			case 'email':
  1920 			case 'email':
  1335 				/*
  1921 				// sanitize_email() validates, which would be unexpected.
  1336 				 * sanitize_email() validates, which would be unexpected.
       
  1337 				 */
       
  1338 				return sanitize_text_field( $value );
  1922 				return sanitize_text_field( $value );
  1339 
  1923 
  1340 			case 'uri':
  1924 			case 'uri':
  1341 				return esc_url_raw( $value );
  1925 				return esc_url_raw( $value );
  1342 
  1926 
  1343 			case 'ip':
  1927 			case 'ip':
  1344 				return sanitize_text_field( $value );
  1928 				return sanitize_text_field( $value );
       
  1929 
       
  1930 			case 'uuid':
       
  1931 				return sanitize_text_field( $value );
  1345 		}
  1932 		}
  1346 	}
  1933 	}
  1347 
  1934 
  1348 	if ( 'string' === $args['type'] ) {
  1935 	if ( 'string' === $args['type'] ) {
  1349 		return strval( $value );
  1936 		return strval( $value );
  1356  * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
  1943  * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
  1357  * Expected to be called in the context of `array_reduce`.
  1944  * Expected to be called in the context of `array_reduce`.
  1358  *
  1945  *
  1359  * @since 5.0.0
  1946  * @since 5.0.0
  1360  *
  1947  *
  1361  * @param  array  $memo Reduce accumulator.
  1948  * @param array  $memo Reduce accumulator.
  1362  * @param  string $path REST API path to preload.
  1949  * @param string $path REST API path to preload.
  1363  * @return array        Modified reduce accumulator.
  1950  * @return array Modified reduce accumulator.
  1364  */
  1951  */
  1365 function rest_preload_api_request( $memo, $path ) {
  1952 function rest_preload_api_request( $memo, $path ) {
  1366 	// array_reduce() doesn't support passing an array in PHP 5.2, so we need to make sure we start with one.
  1953 	// array_reduce() doesn't support passing an array in PHP 5.2,
       
  1954 	// so we need to make sure we start with one.
  1367 	if ( ! is_array( $memo ) ) {
  1955 	if ( ! is_array( $memo ) ) {
  1368 		$memo = array();
  1956 		$memo = array();
  1369 	}
  1957 	}
  1370 
  1958 
  1371 	if ( empty( $path ) ) {
  1959 	if ( empty( $path ) ) {
  1395 
  1983 
  1396 	$response = rest_do_request( $request );
  1984 	$response = rest_do_request( $request );
  1397 	if ( 200 === $response->status ) {
  1985 	if ( 200 === $response->status ) {
  1398 		$server = rest_get_server();
  1986 		$server = rest_get_server();
  1399 		$data   = (array) $response->get_data();
  1987 		$data   = (array) $response->get_data();
  1400 		$links  = $server->get_compact_response_links( $response );
  1988 		$links  = $server::get_compact_response_links( $response );
  1401 		if ( ! empty( $links ) ) {
  1989 		if ( ! empty( $links ) ) {
  1402 			$data['_links'] = $links;
  1990 			$data['_links'] = $links;
  1403 		}
  1991 		}
  1404 
  1992 
  1405 		if ( 'OPTIONS' === $method ) {
  1993 		if ( 'OPTIONS' === $method ) {
  1417 		}
  2005 		}
  1418 	}
  2006 	}
  1419 
  2007 
  1420 	return $memo;
  2008 	return $memo;
  1421 }
  2009 }
       
  2010 
       
  2011 /**
       
  2012  * Parses the "_embed" parameter into the list of resources to embed.
       
  2013  *
       
  2014  * @since 5.4.0
       
  2015  *
       
  2016  * @param string|array $embed Raw "_embed" parameter value.
       
  2017  * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
       
  2018  */
       
  2019 function rest_parse_embed_param( $embed ) {
       
  2020 	if ( ! $embed || 'true' === $embed || '1' === $embed ) {
       
  2021 		return true;
       
  2022 	}
       
  2023 
       
  2024 	$rels = wp_parse_list( $embed );
       
  2025 
       
  2026 	if ( ! $rels ) {
       
  2027 		return true;
       
  2028 	}
       
  2029 
       
  2030 	return $rels;
       
  2031 }
       
  2032 
       
  2033 /**
       
  2034  * Filters the response to remove any fields not available in the given context.
       
  2035  *
       
  2036  * @since 5.5.0
       
  2037  *
       
  2038  * @param array|object $data    The response data to modify.
       
  2039  * @param array        $schema  The schema for the endpoint used to filter the response.
       
  2040  * @param string       $context The requested context.
       
  2041  * @return array|object The filtered response data.
       
  2042  */
       
  2043 function rest_filter_response_by_context( $data, $schema, $context ) {
       
  2044 	if ( ! is_array( $data ) && ! is_object( $data ) ) {
       
  2045 		return $data;
       
  2046 	}
       
  2047 
       
  2048 	if ( isset( $schema['type'] ) ) {
       
  2049 		$type = $schema['type'];
       
  2050 	} elseif ( isset( $schema['properties'] ) ) {
       
  2051 		$type = 'object'; // Back compat if a developer accidentally omitted the type.
       
  2052 	} else {
       
  2053 		return $data;
       
  2054 	}
       
  2055 
       
  2056 	$is_array_type  = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
       
  2057 	$is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
       
  2058 
       
  2059 	if ( $is_array_type && $is_object_type ) {
       
  2060 		if ( rest_is_array( $data ) ) {
       
  2061 			$is_object_type = false;
       
  2062 		} else {
       
  2063 			$is_array_type = false;
       
  2064 		}
       
  2065 	}
       
  2066 
       
  2067 	$has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
       
  2068 
       
  2069 	foreach ( $data as $key => $value ) {
       
  2070 		$check = array();
       
  2071 
       
  2072 		if ( $is_array_type ) {
       
  2073 			$check = isset( $schema['items'] ) ? $schema['items'] : array();
       
  2074 		} elseif ( $is_object_type ) {
       
  2075 			if ( isset( $schema['properties'][ $key ] ) ) {
       
  2076 				$check = $schema['properties'][ $key ];
       
  2077 			} elseif ( $has_additional_properties ) {
       
  2078 				$check = $schema['additionalProperties'];
       
  2079 			}
       
  2080 		}
       
  2081 
       
  2082 		if ( ! isset( $check['context'] ) ) {
       
  2083 			continue;
       
  2084 		}
       
  2085 
       
  2086 		if ( ! in_array( $context, $check['context'], true ) ) {
       
  2087 			if ( $is_array_type ) {
       
  2088 				// All array items share schema, so there's no need to check each one.
       
  2089 				$data = array();
       
  2090 				break;
       
  2091 			}
       
  2092 
       
  2093 			if ( is_object( $data ) ) {
       
  2094 				unset( $data->$key );
       
  2095 			} else {
       
  2096 				unset( $data[ $key ] );
       
  2097 			}
       
  2098 		} elseif ( is_array( $value ) || is_object( $value ) ) {
       
  2099 			$new_value = rest_filter_response_by_context( $value, $check, $context );
       
  2100 
       
  2101 			if ( is_object( $data ) ) {
       
  2102 				$data->$key = $new_value;
       
  2103 			} else {
       
  2104 				$data[ $key ] = $new_value;
       
  2105 			}
       
  2106 		}
       
  2107 	}
       
  2108 
       
  2109 	return $data;
       
  2110 }
       
  2111 
       
  2112 /**
       
  2113  * Sets the "additionalProperties" to false by default for all object definitions in the schema.
       
  2114  *
       
  2115  * @since 5.5.0
       
  2116  *
       
  2117  * @param array $schema The schema to modify.
       
  2118  * @return array The modified schema.
       
  2119  */
       
  2120 function rest_default_additional_properties_to_false( $schema ) {
       
  2121 	$type = (array) $schema['type'];
       
  2122 
       
  2123 	if ( in_array( 'object', $type, true ) ) {
       
  2124 		if ( isset( $schema['properties'] ) ) {
       
  2125 			foreach ( $schema['properties'] as $key => $child_schema ) {
       
  2126 				$schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
       
  2127 			}
       
  2128 		}
       
  2129 
       
  2130 		if ( ! isset( $schema['additionalProperties'] ) ) {
       
  2131 			$schema['additionalProperties'] = false;
       
  2132 		}
       
  2133 	}
       
  2134 
       
  2135 	if ( in_array( 'array', $type, true ) ) {
       
  2136 		if ( isset( $schema['items'] ) ) {
       
  2137 			$schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
       
  2138 		}
       
  2139 	}
       
  2140 
       
  2141 	return $schema;
       
  2142 }
       
  2143 
       
  2144 /**
       
  2145  * Gets the REST API route for a post.
       
  2146  *
       
  2147  * @since 5.5.0
       
  2148  *
       
  2149  * @param int|WP_Post $post Post ID or post object.
       
  2150  * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
       
  2151  */
       
  2152 function rest_get_route_for_post( $post ) {
       
  2153 	$post = get_post( $post );
       
  2154 
       
  2155 	if ( ! $post instanceof WP_Post ) {
       
  2156 		return '';
       
  2157 	}
       
  2158 
       
  2159 	$post_type = get_post_type_object( $post->post_type );
       
  2160 	if ( ! $post_type ) {
       
  2161 		return '';
       
  2162 	}
       
  2163 
       
  2164 	$controller = $post_type->get_rest_controller();
       
  2165 	if ( ! $controller ) {
       
  2166 		return '';
       
  2167 	}
       
  2168 
       
  2169 	$route = '';
       
  2170 
       
  2171 	// The only two controllers that we can detect are the Attachments and Posts controllers.
       
  2172 	if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
       
  2173 		$namespace = 'wp/v2';
       
  2174 		$rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
       
  2175 		$route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
       
  2176 	}
       
  2177 
       
  2178 	/**
       
  2179 	 * Filters the REST API route for a post.
       
  2180 	 *
       
  2181 	 * @since 5.5.0
       
  2182 	 *
       
  2183 	 * @param string  $route The route path.
       
  2184 	 * @param WP_Post $post  The post object.
       
  2185 	 */
       
  2186 	return apply_filters( 'rest_route_for_post', $route, $post );
       
  2187 }
       
  2188 
       
  2189 /**
       
  2190  * Gets the REST API route for a term.
       
  2191  *
       
  2192  * @since 5.5.0
       
  2193  *
       
  2194  * @param int|WP_Term $term Term ID or term object.
       
  2195  * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
       
  2196  */
       
  2197 function rest_get_route_for_term( $term ) {
       
  2198 	$term = get_term( $term );
       
  2199 
       
  2200 	if ( ! $term instanceof WP_Term ) {
       
  2201 		return '';
       
  2202 	}
       
  2203 
       
  2204 	$taxonomy = get_taxonomy( $term->taxonomy );
       
  2205 	if ( ! $taxonomy ) {
       
  2206 		return '';
       
  2207 	}
       
  2208 
       
  2209 	$controller = $taxonomy->get_rest_controller();
       
  2210 	if ( ! $controller ) {
       
  2211 		return '';
       
  2212 	}
       
  2213 
       
  2214 	$route = '';
       
  2215 
       
  2216 	// The only controller that works is the Terms controller.
       
  2217 	if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) {
       
  2218 		$namespace = 'wp/v2';
       
  2219 		$rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
       
  2220 		$route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
       
  2221 	}
       
  2222 
       
  2223 	/**
       
  2224 	 * Filters the REST API route for a term.
       
  2225 	 *
       
  2226 	 * @since 5.5.0
       
  2227 	 *
       
  2228 	 * @param string  $route The route path.
       
  2229 	 * @param WP_Term $term  The term object.
       
  2230 	 */
       
  2231 	return apply_filters( 'rest_route_for_term', $route, $term );
       
  2232 }
       
  2233 
       
  2234 /**
       
  2235  * Gets the REST route for the currently queried object.
       
  2236  *
       
  2237  * @since 5.5.0
       
  2238  *
       
  2239  * @return string The REST route of the resource, or an empty string if no resource identified.
       
  2240  */
       
  2241 function rest_get_queried_resource_route() {
       
  2242 	if ( is_singular() ) {
       
  2243 		$route = rest_get_route_for_post( get_queried_object() );
       
  2244 	} elseif ( is_category() || is_tag() || is_tax() ) {
       
  2245 		$route = rest_get_route_for_term( get_queried_object() );
       
  2246 	} elseif ( is_author() ) {
       
  2247 		$route = '/wp/v2/users/' . get_queried_object_id();
       
  2248 	} else {
       
  2249 		$route = '';
       
  2250 	}
       
  2251 
       
  2252 	/**
       
  2253 	 * Filters the REST route for the currently queried object.
       
  2254 	 *
       
  2255 	 * @since 5.5.0
       
  2256 	 *
       
  2257 	 * @param string $link The route with a leading slash, or an empty string.
       
  2258 	 */
       
  2259 	return apply_filters( 'rest_queried_resource_route', $route );
       
  2260 }