wp/wp-includes/rest-api.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
    92 
    92 
    93 		if ( ! isset( $arg_group['permission_callback'] ) ) {
    93 		if ( ! isset( $arg_group['permission_callback'] ) ) {
    94 			_doing_it_wrong(
    94 			_doing_it_wrong(
    95 				__FUNCTION__,
    95 				__FUNCTION__,
    96 				sprintf(
    96 				sprintf(
    97 					/* translators: 1. The REST API route being registered. 2. The argument name. 3. The suggested function name. */
    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.' ),
    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>',
    99 					'<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
   100 					'<code>permission_callback</code>',
   100 					'<code>permission_callback</code>',
   101 					'<code>__return_true</code>'
   101 					'<code>__return_true</code>'
   102 				),
   102 				),
   128  *                                          'null', the field will not be returned in the response. The function will
   128  *                                          'null', the field will not be returned in the response. The function will
   129  *                                          be passed the prepared object data.
   129  *                                          be passed the prepared object data.
   130  *     @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
   130  *     @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
   131  *                                          is 'null', the value cannot be set or updated. The function will be passed
   131  *                                          is 'null', the value cannot be set or updated. The function will be passed
   132  *                                          the model object, like WP_Post.
   132  *                                          the model object, like WP_Post.
   133  *     @type array|null $schema             Optional. The callback function used to create the schema for this field.
   133  *     @type array|null $schema             Optional. The schema for this field.
   134  *                                          Default is 'null', no schema entry will be returned.
   134  *                                          Default is 'null', no schema entry will be returned.
   135  * }
   135  * }
   136  */
   136  */
   137 function register_rest_field( $object_type, $attribute, $args = array() ) {
   137 function register_rest_field( $object_type, $attribute, $args = array() ) {
   138 	$defaults = array(
   138 	$defaults = array(
   151 		$wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
   151 		$wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
   152 	}
   152 	}
   153 }
   153 }
   154 
   154 
   155 /**
   155 /**
   156  * Registers rewrite rules for the API.
   156  * Registers rewrite rules for the REST API.
   157  *
   157  *
   158  * @since 4.4.0
   158  * @since 4.4.0
   159  *
   159  *
   160  * @see rest_api_register_rewrites()
   160  * @see rest_api_register_rewrites()
   161  * @global WP $wp Current WordPress environment instance.
   161  * @global WP $wp Current WordPress environment instance.
   207 	add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
   207 	add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
   208 	add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
   208 	add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
   209 	add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
   209 	add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
   210 
   210 
   211 	add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
   211 	add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
       
   212 	add_filter( 'rest_index', 'rest_add_application_passwords_to_index' );
   212 }
   213 }
   213 
   214 
   214 /**
   215 /**
   215  * Registers default REST API routes.
   216  * Registers default REST API routes.
   216  *
   217  *
   262 
   263 
   263 	// Users.
   264 	// Users.
   264 	$controller = new WP_REST_Users_Controller;
   265 	$controller = new WP_REST_Users_Controller;
   265 	$controller->register_routes();
   266 	$controller->register_routes();
   266 
   267 
       
   268 	// Application Passwords
       
   269 	$controller = new WP_REST_Application_Passwords_Controller();
       
   270 	$controller->register_routes();
       
   271 
   267 	// Comments.
   272 	// Comments.
   268 	$controller = new WP_REST_Comments_Controller;
   273 	$controller = new WP_REST_Comments_Controller;
   269 	$controller->register_routes();
   274 	$controller->register_routes();
       
   275 
       
   276 	$search_handlers = array(
       
   277 		new WP_REST_Post_Search_Handler(),
       
   278 		new WP_REST_Term_Search_Handler(),
       
   279 		new WP_REST_Post_Format_Search_Handler(),
       
   280 	);
   270 
   281 
   271 	/**
   282 	/**
   272 	 * Filters the search handlers to use in the REST search controller.
   283 	 * Filters the search handlers to use in the REST search controller.
   273 	 *
   284 	 *
   274 	 * @since 5.0.0
   285 	 * @since 5.0.0
   275 	 *
   286 	 *
   276 	 * @param array $search_handlers List of search handlers to use in the controller. Each search
   287 	 * @param array $search_handlers List of search handlers to use in the controller. Each search
   277 	 *                               handler instance must extend the `WP_REST_Search_Handler` class.
   288 	 *                               handler instance must extend the `WP_REST_Search_Handler` class.
   278 	 *                               Default is only a handler for posts.
   289 	 *                               Default is only a handler for posts.
   279 	 */
   290 	 */
   280 	$search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) );
   291 	$search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers );
   281 
   292 
   282 	$controller = new WP_REST_Search_Controller( $search_handlers );
   293 	$controller = new WP_REST_Search_Controller( $search_handlers );
   283 	$controller->register_routes();
   294 	$controller->register_routes();
   284 
   295 
   285 	// Block Renderer.
   296 	// Block Renderer.
   300 
   311 
   301 	// Plugins.
   312 	// Plugins.
   302 	$controller = new WP_REST_Plugins_Controller();
   313 	$controller = new WP_REST_Plugins_Controller();
   303 	$controller->register_routes();
   314 	$controller->register_routes();
   304 
   315 
       
   316 	// Sidebars.
       
   317 	$controller = new WP_REST_Sidebars_Controller();
       
   318 	$controller->register_routes();
       
   319 
       
   320 	// Widget Types.
       
   321 	$controller = new WP_REST_Widget_Types_Controller();
       
   322 	$controller->register_routes();
       
   323 
       
   324 	// Widgets.
       
   325 	$controller = new WP_REST_Widgets_Controller();
       
   326 	$controller->register_routes();
       
   327 
   305 	// Block Directory.
   328 	// Block Directory.
   306 	$controller = new WP_REST_Block_Directory_Controller();
   329 	$controller = new WP_REST_Block_Directory_Controller();
   307 	$controller->register_routes();
   330 	$controller->register_routes();
   308 
   331 
       
   332 	// Pattern Directory.
       
   333 	$controller = new WP_REST_Pattern_Directory_Controller();
       
   334 	$controller->register_routes();
       
   335 
       
   336 	// Site Health.
       
   337 	$site_health = WP_Site_Health::get_instance();
       
   338 	$controller  = new WP_REST_Site_Health_Controller( $site_health );
       
   339 	$controller->register_routes();
   309 }
   340 }
   310 
   341 
   311 /**
   342 /**
   312  * Loads the REST API.
   343  * Loads the REST API.
   313  *
   344  *
   368  * @since 4.4.0
   399  * @since 4.4.0
   369  *
   400  *
   370  * @todo Check if this is even necessary
   401  * @todo Check if this is even necessary
   371  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   402  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   372  *
   403  *
   373  * @param int    $blog_id Optional. Blog ID. Default of null returns URL for current blog.
   404  * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog.
   374  * @param string $path    Optional. REST route. Default '/'.
   405  * @param string   $path    Optional. REST route. Default '/'.
   375  * @param string $scheme  Optional. Sanitization scheme. Default 'rest'.
   406  * @param string   $scheme  Optional. Sanitization scheme. Default 'rest'.
   376  * @return string Full URL to the endpoint.
   407  * @return string Full URL to the endpoint.
   377  */
   408  */
   378 function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
   409 function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
   379 	if ( empty( $path ) ) {
   410 	if ( empty( $path ) ) {
   380 		$path = '/';
   411 		$path = '/';
   424 	 *
   455 	 *
   425 	 * Use this filter to adjust the url returned by the get_rest_url() function.
   456 	 * Use this filter to adjust the url returned by the get_rest_url() function.
   426 	 *
   457 	 *
   427 	 * @since 4.4.0
   458 	 * @since 4.4.0
   428 	 *
   459 	 *
   429 	 * @param string $url     REST URL.
   460 	 * @param string   $url     REST URL.
   430 	 * @param string $path    REST route.
   461 	 * @param string   $path    REST route.
   431 	 * @param int    $blog_id Blog ID.
   462 	 * @param int|null $blog_id Blog ID.
   432 	 * @param string $scheme  Sanitization scheme.
   463 	 * @param string   $scheme  Sanitization scheme.
   433 	 */
   464 	 */
   434 	return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
   465 	return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
   435 }
   466 }
   436 
   467 
   437 /**
   468 /**
   481 
   512 
   482 	if ( empty( $wp_rest_server ) ) {
   513 	if ( empty( $wp_rest_server ) ) {
   483 		/**
   514 		/**
   484 		 * Filters the REST Server Class.
   515 		 * Filters the REST Server Class.
   485 		 *
   516 		 *
   486 		 * This filter allows you to adjust the server class used by the API, using a
   517 		 * This filter allows you to adjust the server class used by the REST API, using a
   487 		 * different class to handle requests.
   518 		 * different class to handle requests.
   488 		 *
   519 		 *
   489 		 * @since 4.4.0
   520 		 * @since 4.4.0
   490 		 *
   521 		 *
   491 		 * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
   522 		 * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
   492 		 */
   523 		 */
   493 		$wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
   524 		$wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
   494 		$wp_rest_server       = new $wp_rest_server_class;
   525 		$wp_rest_server       = new $wp_rest_server_class;
   495 
   526 
   496 		/**
   527 		/**
   497 		 * Fires when preparing to serve an API request.
   528 		 * Fires when preparing to serve a REST API request.
   498 		 *
   529 		 *
   499 		 * Endpoint objects should be created and register their hooks on this action rather
   530 		 * Endpoint objects should be created and register their hooks on this action rather
   500 		 * than another action to ensure they're only loaded when needed.
   531 		 * than another action to ensure they're only loaded when needed.
   501 		 *
   532 		 *
   502 		 * @since 4.4.0
   533 		 * @since 4.4.0
   785 	}
   816 	}
   786 	return $array1;
   817 	return $array1;
   787 }
   818 }
   788 
   819 
   789 /**
   820 /**
   790  * Filter the API response to include only a white-listed set of response object fields.
   821  * Filters the REST API response to include only a white-listed set of response object fields.
   791  *
   822  *
   792  * @since 4.8.0
   823  * @since 4.8.0
   793  *
   824  *
   794  * @param WP_REST_Response $response Current response being served.
   825  * @param WP_REST_Response $response Current response being served.
   795  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
   826  * @param WP_REST_Server   $server   ResponseHandler instance (usually WP_REST_Server).
   995 
  1026 
   996 	// Check the nonce.
  1027 	// Check the nonce.
   997 	$result = wp_verify_nonce( $nonce, 'wp_rest' );
  1028 	$result = wp_verify_nonce( $nonce, 'wp_rest' );
   998 
  1029 
   999 	if ( ! $result ) {
  1030 	if ( ! $result ) {
  1000 		return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
  1031 		return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
  1001 	}
  1032 	}
  1002 
  1033 
  1003 	// Send a refreshed nonce in header.
  1034 	// Send a refreshed nonce in header.
  1004 	rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
  1035 	rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
  1005 
  1036 
  1025 		$wp_rest_auth_cookie = substr( $status_type, 12 );
  1056 		$wp_rest_auth_cookie = substr( $status_type, 12 );
  1026 		return;
  1057 		return;
  1027 	}
  1058 	}
  1028 
  1059 
  1029 	$wp_rest_auth_cookie = true;
  1060 	$wp_rest_auth_cookie = true;
       
  1061 }
       
  1062 
       
  1063 /**
       
  1064  * Collects the status of authenticating with an application password.
       
  1065  *
       
  1066  * @since 5.6.0
       
  1067  * @since 5.7.0 Added the `$app_password` parameter.
       
  1068  *
       
  1069  * @global WP_User|WP_Error|null $wp_rest_application_password_status
       
  1070  * @global string|null $wp_rest_application_password_uuid
       
  1071  *
       
  1072  * @param WP_Error $user_or_error The authenticated user or error instance.
       
  1073  * @param array    $app_password  The Application Password used to authenticate.
       
  1074  */
       
  1075 function rest_application_password_collect_status( $user_or_error, $app_password = array() ) {
       
  1076 	global $wp_rest_application_password_status, $wp_rest_application_password_uuid;
       
  1077 
       
  1078 	$wp_rest_application_password_status = $user_or_error;
       
  1079 
       
  1080 	if ( empty( $app_password['uuid'] ) ) {
       
  1081 		$wp_rest_application_password_uuid = null;
       
  1082 	} else {
       
  1083 		$wp_rest_application_password_uuid = $app_password['uuid'];
       
  1084 	}
       
  1085 }
       
  1086 
       
  1087 /**
       
  1088  * Gets the Application Password used for authenticating the request.
       
  1089  *
       
  1090  * @since 5.7.0
       
  1091  *
       
  1092  * @global string|null $wp_rest_application_password_uuid
       
  1093  *
       
  1094  * @return string|null The App Password UUID, or null if Application Passwords was not used.
       
  1095  */
       
  1096 function rest_get_authenticated_app_password() {
       
  1097 	global $wp_rest_application_password_uuid;
       
  1098 
       
  1099 	return $wp_rest_application_password_uuid;
       
  1100 }
       
  1101 
       
  1102 /**
       
  1103  * Checks for errors when using application password-based authentication.
       
  1104  *
       
  1105  * @since 5.6.0
       
  1106  *
       
  1107  * @global WP_User|WP_Error|null $wp_rest_application_password_status
       
  1108  *
       
  1109  * @param WP_Error|null|true $result Error from another authentication handler,
       
  1110  *                                   null if we should handle it, or another value if not.
       
  1111  * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true.
       
  1112  */
       
  1113 function rest_application_password_check_errors( $result ) {
       
  1114 	global $wp_rest_application_password_status;
       
  1115 
       
  1116 	if ( ! empty( $result ) ) {
       
  1117 		return $result;
       
  1118 	}
       
  1119 
       
  1120 	if ( is_wp_error( $wp_rest_application_password_status ) ) {
       
  1121 		$data = $wp_rest_application_password_status->get_error_data();
       
  1122 
       
  1123 		if ( ! isset( $data['status'] ) ) {
       
  1124 			$data['status'] = 401;
       
  1125 		}
       
  1126 
       
  1127 		$wp_rest_application_password_status->add_data( $data );
       
  1128 
       
  1129 		return $wp_rest_application_password_status;
       
  1130 	}
       
  1131 
       
  1132 	if ( $wp_rest_application_password_status instanceof WP_User ) {
       
  1133 		return true;
       
  1134 	}
       
  1135 
       
  1136 	return $result;
       
  1137 }
       
  1138 
       
  1139 /**
       
  1140  * Adds Application Passwords info to the REST API index.
       
  1141  *
       
  1142  * @since 5.6.0
       
  1143  *
       
  1144  * @param WP_REST_Response $response The index response object.
       
  1145  * @return WP_REST_Response
       
  1146  */
       
  1147 function rest_add_application_passwords_to_index( $response ) {
       
  1148 	if ( ! wp_is_application_passwords_available() ) {
       
  1149 		return $response;
       
  1150 	}
       
  1151 
       
  1152 	$response->data['authentication']['application-passwords'] = array(
       
  1153 		'endpoints' => array(
       
  1154 			'authorization' => admin_url( 'authorize-application.php' ),
       
  1155 		),
       
  1156 	);
       
  1157 
       
  1158 	return $response;
  1030 }
  1159 }
  1031 
  1160 
  1032 /**
  1161 /**
  1033  * Retrieves the avatar urls in various sizes.
  1162  * Retrieves the avatar urls in various sizes.
  1034  *
  1163  *
  1159 /**
  1288 /**
  1160  * Returns a contextual HTTP error code for authorization failure.
  1289  * Returns a contextual HTTP error code for authorization failure.
  1161  *
  1290  *
  1162  * @since 4.7.0
  1291  * @since 4.7.0
  1163  *
  1292  *
  1164  * @return integer 401 if the user is not logged in, 403 if the user is logged in.
  1293  * @return int 401 if the user is not logged in, 403 if the user is logged in.
  1165  */
  1294  */
  1166 function rest_authorization_required_code() {
  1295 function rest_authorization_required_code() {
  1167 	return is_user_logged_in() ? 403 : 401;
  1296 	return is_user_logged_in() ? 403 : 401;
  1168 }
  1297 }
  1169 
  1298 
  1256  * Changes a boolean-like value into the proper boolean value.
  1385  * Changes a boolean-like value into the proper boolean value.
  1257  *
  1386  *
  1258  * @since 4.7.0
  1387  * @since 4.7.0
  1259  *
  1388  *
  1260  * @param bool|string|int $value The value being evaluated.
  1389  * @param bool|string|int $value The value being evaluated.
  1261  * @return boolean Returns the proper associated boolean value.
  1390  * @return bool Returns the proper associated boolean value.
  1262  */
  1391  */
  1263 function rest_sanitize_boolean( $value ) {
  1392 function rest_sanitize_boolean( $value ) {
  1264 	// String values are translated to `true`; make sure 'false' is false.
  1393 	// String values are translated to `true`; make sure 'false' is false.
  1265 	if ( is_string( $value ) ) {
  1394 	if ( is_string( $value ) ) {
  1266 		$value = strtolower( $value );
  1395 		$value = strtolower( $value );
  1277  * Determines if a given value is boolean-like.
  1406  * Determines if a given value is boolean-like.
  1278  *
  1407  *
  1279  * @since 4.7.0
  1408  * @since 4.7.0
  1280  *
  1409  *
  1281  * @param bool|string $maybe_bool The value being evaluated.
  1410  * @param bool|string $maybe_bool The value being evaluated.
  1282  * @return boolean True if a boolean, otherwise false.
  1411  * @return bool True if a boolean, otherwise false.
  1283  */
  1412  */
  1284 function rest_is_boolean( $maybe_bool ) {
  1413 function rest_is_boolean( $maybe_bool ) {
  1285 	if ( is_bool( $maybe_bool ) ) {
  1414 	if ( is_bool( $maybe_bool ) ) {
  1286 		return true;
  1415 		return true;
  1287 	}
  1416 	}
  1313  *
  1442  *
  1314  * @param mixed $maybe_integer The value being evaluated.
  1443  * @param mixed $maybe_integer The value being evaluated.
  1315  * @return bool True if an integer, otherwise false.
  1444  * @return bool True if an integer, otherwise false.
  1316  */
  1445  */
  1317 function rest_is_integer( $maybe_integer ) {
  1446 function rest_is_integer( $maybe_integer ) {
  1318 	return is_numeric( $maybe_integer ) && round( floatval( $maybe_integer ) ) === floatval( $maybe_integer );
  1447 	return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer;
  1319 }
  1448 }
  1320 
  1449 
  1321 /**
  1450 /**
  1322  * Determines if a given value is array-like.
  1451  * Determines if a given value is array-like.
  1323  *
  1452  *
  1460 	$invalid_types = array_diff( $args['type'], $allowed_types );
  1589 	$invalid_types = array_diff( $args['type'], $allowed_types );
  1461 
  1590 
  1462 	if ( $invalid_types ) {
  1591 	if ( $invalid_types ) {
  1463 		_doing_it_wrong(
  1592 		_doing_it_wrong(
  1464 			__FUNCTION__,
  1593 			__FUNCTION__,
  1465 			/* translators: 1. Parameter. 2. List of allowed types. */
  1594 			/* 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 ),
  1595 			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'
  1596 			'5.5.0'
  1468 		);
  1597 		);
  1469 	}
  1598 	}
  1470 
  1599 
  1535 	foreach ( $value as $k => $v ) {
  1664 	foreach ( $value as $k => $v ) {
  1536 		$value[ $k ] = rest_stabilize_value( $v );
  1665 		$value[ $k ] = rest_stabilize_value( $v );
  1537 	}
  1666 	}
  1538 
  1667 
  1539 	return $value;
  1668 	return $value;
       
  1669 }
       
  1670 
       
  1671 /**
       
  1672  * Validates if the JSON Schema pattern matches a value.
       
  1673  *
       
  1674  * @since 5.6.0
       
  1675  *
       
  1676  * @param string $pattern The pattern to match against.
       
  1677  * @param string $value   The value to check.
       
  1678  * @return bool           True if the pattern matches the given value, false otherwise.
       
  1679  */
       
  1680 function rest_validate_json_schema_pattern( $pattern, $value ) {
       
  1681 	$escaped_pattern = str_replace( '#', '\\#', $pattern );
       
  1682 
       
  1683 	return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
       
  1684 }
       
  1685 
       
  1686 /**
       
  1687  * Finds the schema for a property using the patternProperties keyword.
       
  1688  *
       
  1689  * @since 5.6.0
       
  1690  *
       
  1691  * @param string $property The property name to check.
       
  1692  * @param array  $args     The schema array to use.
       
  1693  * @return array|null      The schema of matching pattern property, or null if no patterns match.
       
  1694  */
       
  1695 function rest_find_matching_pattern_property_schema( $property, $args ) {
       
  1696 	if ( isset( $args['patternProperties'] ) ) {
       
  1697 		foreach ( $args['patternProperties'] as $pattern => $child_schema ) {
       
  1698 			if ( rest_validate_json_schema_pattern( $pattern, $property ) ) {
       
  1699 				return $child_schema;
       
  1700 			}
       
  1701 		}
       
  1702 	}
       
  1703 
       
  1704 	return null;
       
  1705 }
       
  1706 
       
  1707 /**
       
  1708  * Formats a combining operation error into a WP_Error object.
       
  1709  *
       
  1710  * @since 5.6.0
       
  1711  *
       
  1712  * @param string $param The parameter name.
       
  1713  * @param array $error  The error details.
       
  1714  * @return WP_Error
       
  1715  */
       
  1716 function rest_format_combining_operation_error( $param, $error ) {
       
  1717 	$position = $error['index'];
       
  1718 	$reason   = $error['error_object']->get_error_message();
       
  1719 
       
  1720 	if ( isset( $error['schema']['title'] ) ) {
       
  1721 		$title = $error['schema']['title'];
       
  1722 
       
  1723 		return new WP_Error(
       
  1724 			'rest_no_matching_schema',
       
  1725 			/* translators: 1: Parameter, 2: Schema title, 3: Reason. */
       
  1726 			sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
       
  1727 			array( 'position' => $position )
       
  1728 		);
       
  1729 	}
       
  1730 
       
  1731 	return new WP_Error(
       
  1732 		'rest_no_matching_schema',
       
  1733 		/* translators: 1: Parameter, 2: Reason. */
       
  1734 		sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
       
  1735 		array( 'position' => $position )
       
  1736 	);
       
  1737 }
       
  1738 
       
  1739 /**
       
  1740  * Gets the error of combining operation.
       
  1741  *
       
  1742  * @since 5.6.0
       
  1743  *
       
  1744  * @param array  $value  The value to validate.
       
  1745  * @param string $param  The parameter name, used in error messages.
       
  1746  * @param array  $errors The errors array, to search for possible error.
       
  1747  * @return WP_Error      The combining operation error.
       
  1748  */
       
  1749 function rest_get_combining_operation_error( $value, $param, $errors ) {
       
  1750 	// If there is only one error, simply return it.
       
  1751 	if ( 1 === count( $errors ) ) {
       
  1752 		return rest_format_combining_operation_error( $param, $errors[0] );
       
  1753 	}
       
  1754 
       
  1755 	// Filter out all errors related to type validation.
       
  1756 	$filtered_errors = array();
       
  1757 	foreach ( $errors as $error ) {
       
  1758 		$error_code = $error['error_object']->get_error_code();
       
  1759 		$error_data = $error['error_object']->get_error_data();
       
  1760 
       
  1761 		if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
       
  1762 			$filtered_errors[] = $error;
       
  1763 		}
       
  1764 	}
       
  1765 
       
  1766 	// If there is only one error left, simply return it.
       
  1767 	if ( 1 === count( $filtered_errors ) ) {
       
  1768 		return rest_format_combining_operation_error( $param, $filtered_errors[0] );
       
  1769 	}
       
  1770 
       
  1771 	// If there are only errors related to object validation, try choosing the most appropriate one.
       
  1772 	if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
       
  1773 		$result = null;
       
  1774 		$number = 0;
       
  1775 
       
  1776 		foreach ( $filtered_errors as $error ) {
       
  1777 			if ( isset( $error['schema']['properties'] ) ) {
       
  1778 				$n = count( array_intersect_key( $error['schema']['properties'], $value ) );
       
  1779 				if ( $n > $number ) {
       
  1780 					$result = $error;
       
  1781 					$number = $n;
       
  1782 				}
       
  1783 			}
       
  1784 		}
       
  1785 
       
  1786 		if ( null !== $result ) {
       
  1787 			return rest_format_combining_operation_error( $param, $result );
       
  1788 		}
       
  1789 	}
       
  1790 
       
  1791 	// If each schema has a title, include those titles in the error message.
       
  1792 	$schema_titles = array();
       
  1793 	foreach ( $errors as $error ) {
       
  1794 		if ( isset( $error['schema']['title'] ) ) {
       
  1795 			$schema_titles[] = $error['schema']['title'];
       
  1796 		}
       
  1797 	}
       
  1798 
       
  1799 	if ( count( $schema_titles ) === count( $errors ) ) {
       
  1800 		/* translators: 1: Parameter, 2: Schema titles. */
       
  1801 		return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
       
  1802 	}
       
  1803 
       
  1804 	/* translators: %s: Parameter. */
       
  1805 	return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) );
       
  1806 }
       
  1807 
       
  1808 /**
       
  1809  * Finds the matching schema among the "anyOf" schemas.
       
  1810  *
       
  1811  * @since 5.6.0
       
  1812  *
       
  1813  * @param mixed  $value   The value to validate.
       
  1814  * @param array  $args    The schema array to use.
       
  1815  * @param string $param   The parameter name, used in error messages.
       
  1816  * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
       
  1817  */
       
  1818 function rest_find_any_matching_schema( $value, $args, $param ) {
       
  1819 	$errors = array();
       
  1820 
       
  1821 	foreach ( $args['anyOf'] as $index => $schema ) {
       
  1822 		if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
       
  1823 			$schema['type'] = $args['type'];
       
  1824 		}
       
  1825 
       
  1826 		$is_valid = rest_validate_value_from_schema( $value, $schema, $param );
       
  1827 		if ( ! is_wp_error( $is_valid ) ) {
       
  1828 			return $schema;
       
  1829 		}
       
  1830 
       
  1831 		$errors[] = array(
       
  1832 			'error_object' => $is_valid,
       
  1833 			'schema'       => $schema,
       
  1834 			'index'        => $index,
       
  1835 		);
       
  1836 	}
       
  1837 
       
  1838 	return rest_get_combining_operation_error( $value, $param, $errors );
       
  1839 }
       
  1840 
       
  1841 /**
       
  1842  * Finds the matching schema among the "oneOf" schemas.
       
  1843  *
       
  1844  * @since 5.6.0
       
  1845  *
       
  1846  * @param mixed  $value                  The value to validate.
       
  1847  * @param array  $args                   The schema array to use.
       
  1848  * @param string $param                  The parameter name, used in error messages.
       
  1849  * @param bool   $stop_after_first_match Optional. Whether the process should stop after the first successful match.
       
  1850  * @return array|WP_Error                The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
       
  1851  */
       
  1852 function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
       
  1853 	$matching_schemas = array();
       
  1854 	$errors           = array();
       
  1855 
       
  1856 	foreach ( $args['oneOf'] as $index => $schema ) {
       
  1857 		if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
       
  1858 			$schema['type'] = $args['type'];
       
  1859 		}
       
  1860 
       
  1861 		$is_valid = rest_validate_value_from_schema( $value, $schema, $param );
       
  1862 		if ( ! is_wp_error( $is_valid ) ) {
       
  1863 			if ( $stop_after_first_match ) {
       
  1864 				return $schema;
       
  1865 			}
       
  1866 
       
  1867 			$matching_schemas[] = array(
       
  1868 				'schema_object' => $schema,
       
  1869 				'index'         => $index,
       
  1870 			);
       
  1871 		} else {
       
  1872 			$errors[] = array(
       
  1873 				'error_object' => $is_valid,
       
  1874 				'schema'       => $schema,
       
  1875 				'index'        => $index,
       
  1876 			);
       
  1877 		}
       
  1878 	}
       
  1879 
       
  1880 	if ( ! $matching_schemas ) {
       
  1881 		return rest_get_combining_operation_error( $value, $param, $errors );
       
  1882 	}
       
  1883 
       
  1884 	if ( count( $matching_schemas ) > 1 ) {
       
  1885 		$schema_positions = array();
       
  1886 		$schema_titles    = array();
       
  1887 
       
  1888 		foreach ( $matching_schemas as $schema ) {
       
  1889 			$schema_positions[] = $schema['index'];
       
  1890 
       
  1891 			if ( isset( $schema['schema_object']['title'] ) ) {
       
  1892 				$schema_titles[] = $schema['schema_object']['title'];
       
  1893 			}
       
  1894 		}
       
  1895 
       
  1896 		// If each schema has a title, include those titles in the error message.
       
  1897 		if ( count( $schema_titles ) === count( $matching_schemas ) ) {
       
  1898 			return new WP_Error(
       
  1899 				'rest_one_of_multiple_matches',
       
  1900 				/* translators: 1: Parameter, 2: Schema titles. */
       
  1901 				wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
       
  1902 				array( 'positions' => $schema_positions )
       
  1903 			);
       
  1904 		}
       
  1905 
       
  1906 		return new WP_Error(
       
  1907 			'rest_one_of_multiple_matches',
       
  1908 			/* translators: %s: Parameter. */
       
  1909 			sprintf( __( '%s matches more than one of the expected formats.' ), $param ),
       
  1910 			array( 'positions' => $schema_positions )
       
  1911 		);
       
  1912 	}
       
  1913 
       
  1914 	return $matching_schemas[0]['schema_object'];
       
  1915 }
       
  1916 
       
  1917 /**
       
  1918  * Checks the equality of two values, following JSON Schema semantics.
       
  1919  *
       
  1920  * Property order is ignored for objects.
       
  1921  *
       
  1922  * Values must have been previously sanitized/coerced to their native types.
       
  1923  *
       
  1924  * @since 5.7.0
       
  1925  *
       
  1926  * @param mixed $value1 The first value to check.
       
  1927  * @param mixed $value2 The second value to check.
       
  1928  * @return bool True if the values are equal or false otherwise.
       
  1929  */
       
  1930 function rest_are_values_equal( $value1, $value2 ) {
       
  1931 	if ( is_array( $value1 ) && is_array( $value2 ) ) {
       
  1932 		if ( count( $value1 ) !== count( $value2 ) ) {
       
  1933 			return false;
       
  1934 		}
       
  1935 
       
  1936 		foreach ( $value1 as $index => $value ) {
       
  1937 			if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
       
  1938 				return false;
       
  1939 			}
       
  1940 		}
       
  1941 
       
  1942 		return true;
       
  1943 	}
       
  1944 
       
  1945 	if ( is_int( $value1 ) && is_float( $value2 )
       
  1946 		|| is_float( $value1 ) && is_int( $value2 )
       
  1947 	) {
       
  1948 		return (float) $value1 === (float) $value2;
       
  1949 	}
       
  1950 
       
  1951 	return $value1 === $value2;
       
  1952 }
       
  1953 
       
  1954 /**
       
  1955  * Validates that the given value is a member of the JSON Schema "enum".
       
  1956  *
       
  1957  * @since 5.7.0
       
  1958  *
       
  1959  * @param mixed  $value  The value to validate.
       
  1960  * @param array  $args   The schema array to use.
       
  1961  * @param string $param  The parameter name, used in error messages.
       
  1962  * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
       
  1963  */
       
  1964 function rest_validate_enum( $value, $args, $param ) {
       
  1965 	$sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
       
  1966 	if ( is_wp_error( $sanitized_value ) ) {
       
  1967 		return $sanitized_value;
       
  1968 	}
       
  1969 
       
  1970 	foreach ( $args['enum'] as $enum_value ) {
       
  1971 		if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
       
  1972 			return true;
       
  1973 		}
       
  1974 	}
       
  1975 
       
  1976 	$encoded_enum_values = array();
       
  1977 	foreach ( $args['enum'] as $enum_value ) {
       
  1978 		$encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
       
  1979 	}
       
  1980 
       
  1981 	if ( count( $encoded_enum_values ) === 1 ) {
       
  1982 		/* translators: 1: Parameter, 2: Valid values. */
       
  1983 		return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
       
  1984 	}
       
  1985 
       
  1986 	/* translators: 1: Parameter, 2: List of valid values. */
       
  1987 	return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
       
  1988 }
       
  1989 
       
  1990 /**
       
  1991  * Get all valid JSON schema properties.
       
  1992  *
       
  1993  * @since 5.6.0
       
  1994  *
       
  1995  * @return string[] All valid JSON schema properties.
       
  1996  */
       
  1997 function rest_get_allowed_schema_keywords() {
       
  1998 	return array(
       
  1999 		'title',
       
  2000 		'description',
       
  2001 		'default',
       
  2002 		'type',
       
  2003 		'format',
       
  2004 		'enum',
       
  2005 		'items',
       
  2006 		'properties',
       
  2007 		'additionalProperties',
       
  2008 		'patternProperties',
       
  2009 		'minProperties',
       
  2010 		'maxProperties',
       
  2011 		'minimum',
       
  2012 		'maximum',
       
  2013 		'exclusiveMinimum',
       
  2014 		'exclusiveMaximum',
       
  2015 		'multipleOf',
       
  2016 		'minLength',
       
  2017 		'maxLength',
       
  2018 		'pattern',
       
  2019 		'minItems',
       
  2020 		'maxItems',
       
  2021 		'uniqueItems',
       
  2022 		'anyOf',
       
  2023 		'oneOf',
       
  2024 	);
  1540 }
  2025 }
  1541 
  2026 
  1542 /**
  2027 /**
  1543  * Validate a value based on a schema.
  2028  * Validate a value based on a schema.
  1544  *
  2029  *
  1549  * @since 5.4.0 Convert an empty string to an empty object.
  2034  * @since 5.4.0 Convert an empty string to an empty object.
  1550  * @since 5.5.0 Add the "uuid" and "hex-color" formats.
  2035  * @since 5.5.0 Add the "uuid" and "hex-color" formats.
  1551  *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
  2036  *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
  1552  *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
  2037  *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
  1553  *              Validate required properties.
  2038  *              Validate required properties.
       
  2039  * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
       
  2040  *              Support the "multipleOf" keyword for numbers and integers.
       
  2041  *              Support the "patternProperties" keyword for objects.
       
  2042  *              Support the "anyOf" and "oneOf" keywords.
  1554  *
  2043  *
  1555  * @param mixed  $value The value to validate.
  2044  * @param mixed  $value The value to validate.
  1556  * @param array  $args  Schema array to use for validation.
  2045  * @param array  $args  Schema array to use for validation.
  1557  * @param string $param The parameter name, used in error messages.
  2046  * @param string $param The parameter name, used in error messages.
  1558  * @return true|WP_Error
  2047  * @return true|WP_Error
  1559  */
  2048  */
  1560 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
  2049 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
       
  2050 	if ( isset( $args['anyOf'] ) ) {
       
  2051 		$matching_schema = rest_find_any_matching_schema( $value, $args, $param );
       
  2052 		if ( is_wp_error( $matching_schema ) ) {
       
  2053 			return $matching_schema;
       
  2054 		}
       
  2055 
       
  2056 		if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
       
  2057 			$args['type'] = $matching_schema['type'];
       
  2058 		}
       
  2059 	}
       
  2060 
       
  2061 	if ( isset( $args['oneOf'] ) ) {
       
  2062 		$matching_schema = rest_find_one_matching_schema( $value, $args, $param );
       
  2063 		if ( is_wp_error( $matching_schema ) ) {
       
  2064 			return $matching_schema;
       
  2065 		}
       
  2066 
       
  2067 		if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
       
  2068 			$args['type'] = $matching_schema['type'];
       
  2069 		}
       
  2070 	}
       
  2071 
  1561 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
  2072 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
  1562 
  2073 
  1563 	if ( ! isset( $args['type'] ) ) {
  2074 	if ( ! isset( $args['type'] ) ) {
  1564 		/* translators: 1. Parameter */
  2075 		/* translators: %s: Parameter. */
  1565 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
  2076 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
  1566 	}
  2077 	}
  1567 
  2078 
  1568 	if ( is_array( $args['type'] ) ) {
  2079 	if ( is_array( $args['type'] ) ) {
  1569 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
  2080 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
  1570 
  2081 
  1571 		if ( ! $best_type ) {
  2082 		if ( ! $best_type ) {
  1572 			/* translators: 1: Parameter, 2: List of types. */
  2083 			return new WP_Error(
  1573 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
  2084 				'rest_invalid_type',
       
  2085 				/* translators: 1: Parameter, 2: List of types. */
       
  2086 				sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
       
  2087 				array( 'param' => $param )
       
  2088 			);
  1574 		}
  2089 		}
  1575 
  2090 
  1576 		$args['type'] = $best_type;
  2091 		$args['type'] = $best_type;
  1577 	}
  2092 	}
  1578 
  2093 
  1579 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
  2094 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
  1580 		_doing_it_wrong(
  2095 		_doing_it_wrong(
  1581 			__FUNCTION__,
  2096 			__FUNCTION__,
  1582 			/* translators: 1. Parameter 2. The list of allowed types. */
  2097 			/* 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 ),
  2098 			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'
  2099 			'5.5.0'
  1585 		);
  2100 		);
  1586 	}
  2101 	}
  1587 
  2102 
  1588 	if ( 'array' === $args['type'] ) {
  2103 	switch ( $args['type'] ) {
  1589 		if ( ! rest_is_array( $value ) ) {
  2104 		case 'null':
  1590 			/* translators: 1: Parameter, 2: Type name. */
  2105 			$is_valid = rest_validate_null_value_from_schema( $value, $param );
  1591 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  2106 			break;
  1592 		}
  2107 		case 'boolean':
  1593 
  2108 			$is_valid = rest_validate_boolean_value_from_schema( $value, $param );
  1594 		$value = rest_sanitize_array( $value );
  2109 			break;
  1595 
  2110 		case 'object':
  1596 		if ( isset( $args['items'] ) ) {
  2111 			$is_valid = rest_validate_object_value_from_schema( $value, $args, $param );
  1597 			foreach ( $value as $index => $v ) {
  2112 			break;
  1598 				$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  2113 		case 'array':
  1599 				if ( is_wp_error( $is_valid ) ) {
  2114 			$is_valid = rest_validate_array_value_from_schema( $value, $args, $param );
  1600 					return $is_valid;
  2115 			break;
  1601 				}
  2116 		case 'number':
  1602 			}
  2117 			$is_valid = rest_validate_number_value_from_schema( $value, $args, $param );
  1603 		}
  2118 			break;
  1604 
  2119 		case 'string':
  1605 		if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
  2120 			$is_valid = rest_validate_string_value_from_schema( $value, $args, $param );
  1606 			/* translators: 1: Parameter, 2: Number. */
  2121 			break;
  1607 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) );
  2122 		case 'integer':
  1608 		}
  2123 			$is_valid = rest_validate_integer_value_from_schema( $value, $args, $param );
  1609 
  2124 			break;
  1610 		if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
  2125 		default:
  1611 			/* translators: 1: Parameter, 2: Number. */
  2126 			$is_valid = true;
  1612 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) );
  2127 			break;
  1613 		}
  2128 	}
  1614 
  2129 
  1615 		if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
  2130 	if ( is_wp_error( $is_valid ) ) {
  1616 			/* translators: 1: Parameter */
  2131 		return $is_valid;
  1617 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
       
  1618 		}
       
  1619 	}
       
  1620 
       
  1621 	if ( 'object' === $args['type'] ) {
       
  1622 		if ( ! rest_is_object( $value ) ) {
       
  1623 			/* translators: 1: Parameter, 2: Type name. */
       
  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 			}
       
  1643 		}
       
  1644 
       
  1645 		foreach ( $value as $property => $v ) {
       
  1646 			if ( isset( $args['properties'][ $property ] ) ) {
       
  1647 				$is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
       
  1648 				if ( is_wp_error( $is_valid ) ) {
       
  1649 					return $is_valid;
       
  1650 				}
       
  1651 			} elseif ( isset( $args['additionalProperties'] ) ) {
       
  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 				}
       
  1663 			}
       
  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;
       
  1674 	}
  2132 	}
  1675 
  2133 
  1676 	if ( ! empty( $args['enum'] ) ) {
  2134 	if ( ! empty( $args['enum'] ) ) {
  1677 		if ( ! in_array( $value, $args['enum'], true ) ) {
  2135 		$enum_contains_value = rest_validate_enum( $value, $args, $param );
  1678 			/* translators: 1: Parameter, 2: List of valid values. */
  2136 		if ( is_wp_error( $enum_contains_value ) ) {
  1679 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
  2137 			return $enum_contains_value;
  1680 		}
       
  1681 	}
       
  1682 
       
  1683 	if ( in_array( $args['type'], array( 'integer', 'number' ), true ) && ! is_numeric( $value ) ) {
       
  1684 		/* translators: 1: Parameter, 2: Type name. */
       
  1685 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
       
  1686 	}
       
  1687 
       
  1688 	if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
       
  1689 		/* translators: 1: Parameter, 2: Type name. */
       
  1690 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
       
  1691 	}
       
  1692 
       
  1693 	if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
       
  1694 		/* translators: 1: Parameter, 2: Type name. */
       
  1695 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
       
  1696 	}
       
  1697 
       
  1698 	if ( 'string' === $args['type'] ) {
       
  1699 		if ( ! is_string( $value ) ) {
       
  1700 			/* translators: 1: Parameter, 2: Type name. */
       
  1701 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
       
  1702 		}
       
  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 		}
  2138 		}
  1735 	}
  2139 	}
  1736 
  2140 
  1737 	// The "format" keyword should only be applied to strings. However, for backward compatibility,
  2141 	// 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.
  2142 	// we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
  1758 				}
  2162 				}
  1759 				break;
  2163 				break;
  1760 			case 'ip':
  2164 			case 'ip':
  1761 				if ( ! rest_is_ip_address( $value ) ) {
  2165 				if ( ! rest_is_ip_address( $value ) ) {
  1762 					/* translators: %s: IP address. */
  2166 					/* translators: %s: IP address. */
  1763 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
  2167 					return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) );
  1764 				}
  2168 				}
  1765 				break;
  2169 				break;
  1766 			case 'uuid':
  2170 			case 'uuid':
  1767 				if ( ! wp_is_uuid( $value ) ) {
  2171 				if ( ! wp_is_uuid( $value ) ) {
  1768 					/* translators: %s is the name of a JSON field expecting a valid uuid. */
  2172 					/* translators: %s: 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 ) );
  2173 					return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
  1770 				}
  2174 				}
  1771 				break;
  2175 				break;
  1772 		}
  2176 		}
  1773 	}
  2177 	}
  1774 
  2178 
  1775 	if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
  2179 	return true;
  1776 		if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
  2180 }
  1777 			if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
  2181 
       
  2182 /**
       
  2183  * Validates a null value based on a schema.
       
  2184  *
       
  2185  * @since 5.7.0
       
  2186  *
       
  2187  * @param mixed  $value The value to validate.
       
  2188  * @param string $param The parameter name, used in error messages.
       
  2189  * @return true|WP_Error
       
  2190  */
       
  2191 function rest_validate_null_value_from_schema( $value, $param ) {
       
  2192 	if ( null !== $value ) {
       
  2193 		return new WP_Error(
       
  2194 			'rest_invalid_type',
       
  2195 			/* translators: 1: Parameter, 2: Type name. */
       
  2196 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
       
  2197 			array( 'param' => $param )
       
  2198 		);
       
  2199 	}
       
  2200 
       
  2201 	return true;
       
  2202 }
       
  2203 
       
  2204 /**
       
  2205  * Validates a boolean value based on a schema.
       
  2206  *
       
  2207  * @since 5.7.0
       
  2208  *
       
  2209  * @param mixed  $value The value to validate.
       
  2210  * @param string $param The parameter name, used in error messages.
       
  2211  * @return true|WP_Error
       
  2212  */
       
  2213 function rest_validate_boolean_value_from_schema( $value, $param ) {
       
  2214 	if ( ! rest_is_boolean( $value ) ) {
       
  2215 		return new WP_Error(
       
  2216 			'rest_invalid_type',
       
  2217 			/* translators: 1: Parameter, 2: Type name. */
       
  2218 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
       
  2219 			array( 'param' => $param )
       
  2220 		);
       
  2221 	}
       
  2222 
       
  2223 	return true;
       
  2224 }
       
  2225 
       
  2226 /**
       
  2227  * Validates an object value based on a schema.
       
  2228  *
       
  2229  * @since 5.7.0
       
  2230  *
       
  2231  * @param mixed  $value The value to validate.
       
  2232  * @param array  $args  Schema array to use for validation.
       
  2233  * @param string $param The parameter name, used in error messages.
       
  2234  * @return true|WP_Error
       
  2235  */
       
  2236 function rest_validate_object_value_from_schema( $value, $args, $param ) {
       
  2237 	if ( ! rest_is_object( $value ) ) {
       
  2238 		return new WP_Error(
       
  2239 			'rest_invalid_type',
       
  2240 			/* translators: 1: Parameter, 2: Type name. */
       
  2241 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
       
  2242 			array( 'param' => $param )
       
  2243 		);
       
  2244 	}
       
  2245 
       
  2246 	$value = rest_sanitize_object( $value );
       
  2247 
       
  2248 	if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
       
  2249 		foreach ( $args['required'] as $name ) {
       
  2250 			if ( ! array_key_exists( $name, $value ) ) {
       
  2251 				return new WP_Error(
       
  2252 					'rest_property_required',
       
  2253 					/* translators: 1: Property of an object, 2: Parameter. */
       
  2254 					sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
       
  2255 				);
       
  2256 			}
       
  2257 		}
       
  2258 	} elseif ( isset( $args['properties'] ) ) { // schema version 3
       
  2259 		foreach ( $args['properties'] as $name => $property ) {
       
  2260 			if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
       
  2261 				return new WP_Error(
       
  2262 					'rest_property_required',
       
  2263 					/* translators: 1: Property of an object, 2: Parameter. */
       
  2264 					sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
       
  2265 				);
       
  2266 			}
       
  2267 		}
       
  2268 	}
       
  2269 
       
  2270 	foreach ( $value as $property => $v ) {
       
  2271 		if ( isset( $args['properties'][ $property ] ) ) {
       
  2272 			$is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
       
  2273 			if ( is_wp_error( $is_valid ) ) {
       
  2274 				return $is_valid;
       
  2275 			}
       
  2276 			continue;
       
  2277 		}
       
  2278 
       
  2279 		$pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
       
  2280 		if ( null !== $pattern_property_schema ) {
       
  2281 			$is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
       
  2282 			if ( is_wp_error( $is_valid ) ) {
       
  2283 				return $is_valid;
       
  2284 			}
       
  2285 			continue;
       
  2286 		}
       
  2287 
       
  2288 		if ( isset( $args['additionalProperties'] ) ) {
       
  2289 			if ( false === $args['additionalProperties'] ) {
       
  2290 				return new WP_Error(
       
  2291 					'rest_additional_properties_forbidden',
       
  2292 					/* translators: %s: Property of an object. */
       
  2293 					sprintf( __( '%1$s is not a valid property of Object.' ), $property )
       
  2294 				);
       
  2295 			}
       
  2296 
       
  2297 			if ( is_array( $args['additionalProperties'] ) ) {
       
  2298 				$is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
       
  2299 				if ( is_wp_error( $is_valid ) ) {
       
  2300 					return $is_valid;
       
  2301 				}
       
  2302 			}
       
  2303 		}
       
  2304 	}
       
  2305 
       
  2306 	if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
       
  2307 		return new WP_Error(
       
  2308 			'rest_too_few_properties',
       
  2309 			sprintf(
       
  2310 				/* translators: 1: Parameter, 2: Number. */
       
  2311 				_n(
       
  2312 					'%1$s must contain at least %2$s property.',
       
  2313 					'%1$s must contain at least %2$s properties.',
       
  2314 					$args['minProperties']
       
  2315 				),
       
  2316 				$param,
       
  2317 				number_format_i18n( $args['minProperties'] )
       
  2318 			)
       
  2319 		);
       
  2320 	}
       
  2321 
       
  2322 	if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
       
  2323 		return new WP_Error(
       
  2324 			'rest_too_many_properties',
       
  2325 			sprintf(
       
  2326 				/* translators: 1: Parameter, 2: Number. */
       
  2327 				_n(
       
  2328 					'%1$s must contain at most %2$s property.',
       
  2329 					'%1$s must contain at most %2$s properties.',
       
  2330 					$args['maxProperties']
       
  2331 				),
       
  2332 				$param,
       
  2333 				number_format_i18n( $args['maxProperties'] )
       
  2334 			)
       
  2335 		);
       
  2336 	}
       
  2337 
       
  2338 	return true;
       
  2339 }
       
  2340 
       
  2341 /**
       
  2342  * Validates an array value based on a schema.
       
  2343  *
       
  2344  * @since 5.7.0
       
  2345  *
       
  2346  * @param mixed  $value The value to validate.
       
  2347  * @param array  $args  Schema array to use for validation.
       
  2348  * @param string $param The parameter name, used in error messages.
       
  2349  * @return true|WP_Error
       
  2350  */
       
  2351 function rest_validate_array_value_from_schema( $value, $args, $param ) {
       
  2352 	if ( ! rest_is_array( $value ) ) {
       
  2353 		return new WP_Error(
       
  2354 			'rest_invalid_type',
       
  2355 			/* translators: 1: Parameter, 2: Type name. */
       
  2356 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
       
  2357 			array( 'param' => $param )
       
  2358 		);
       
  2359 	}
       
  2360 
       
  2361 	$value = rest_sanitize_array( $value );
       
  2362 
       
  2363 	if ( isset( $args['items'] ) ) {
       
  2364 		foreach ( $value as $index => $v ) {
       
  2365 			$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
       
  2366 			if ( is_wp_error( $is_valid ) ) {
       
  2367 				return $is_valid;
       
  2368 			}
       
  2369 		}
       
  2370 	}
       
  2371 
       
  2372 	if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
       
  2373 		return new WP_Error(
       
  2374 			'rest_too_few_items',
       
  2375 			sprintf(
       
  2376 				/* translators: 1: Parameter, 2: Number. */
       
  2377 				_n(
       
  2378 					'%1$s must contain at least %2$s item.',
       
  2379 					'%1$s must contain at least %2$s items.',
       
  2380 					$args['minItems']
       
  2381 				),
       
  2382 				$param,
       
  2383 				number_format_i18n( $args['minItems'] )
       
  2384 			)
       
  2385 		);
       
  2386 	}
       
  2387 
       
  2388 	if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
       
  2389 		return new WP_Error(
       
  2390 			'rest_too_many_items',
       
  2391 			sprintf(
       
  2392 				/* translators: 1: Parameter, 2: Number. */
       
  2393 				_n(
       
  2394 					'%1$s must contain at most %2$s item.',
       
  2395 					'%1$s must contain at most %2$s items.',
       
  2396 					$args['maxItems']
       
  2397 				),
       
  2398 				$param,
       
  2399 				number_format_i18n( $args['maxItems'] )
       
  2400 			)
       
  2401 		);
       
  2402 	}
       
  2403 
       
  2404 	if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
       
  2405 		/* translators: %s: Parameter. */
       
  2406 		return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
       
  2407 	}
       
  2408 
       
  2409 	return true;
       
  2410 }
       
  2411 
       
  2412 /**
       
  2413  * Validates a number value based on a schema.
       
  2414  *
       
  2415  * @since 5.7.0
       
  2416  *
       
  2417  * @param mixed  $value The value to validate.
       
  2418  * @param array  $args  Schema array to use for validation.
       
  2419  * @param string $param The parameter name, used in error messages.
       
  2420  * @return true|WP_Error
       
  2421  */
       
  2422 function rest_validate_number_value_from_schema( $value, $args, $param ) {
       
  2423 	if ( ! is_numeric( $value ) ) {
       
  2424 		return new WP_Error(
       
  2425 			'rest_invalid_type',
       
  2426 			/* translators: 1: Parameter, 2: Type name. */
       
  2427 			sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
       
  2428 			array( 'param' => $param )
       
  2429 		);
       
  2430 	}
       
  2431 
       
  2432 	if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
       
  2433 		return new WP_Error(
       
  2434 			'rest_invalid_multiple',
       
  2435 			/* translators: 1: Parameter, 2: Multiplier. */
       
  2436 			sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] )
       
  2437 		);
       
  2438 	}
       
  2439 
       
  2440 	if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
       
  2441 		if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
       
  2442 			return new WP_Error(
       
  2443 				'rest_out_of_bounds',
  1778 				/* translators: 1: Parameter, 2: Minimum number. */
  2444 				/* translators: 1: Parameter, 2: Minimum number. */
  1779 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
  2445 				sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] )
  1780 			} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
  2446 			);
       
  2447 		}
       
  2448 
       
  2449 		if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
       
  2450 			return new WP_Error(
       
  2451 				'rest_out_of_bounds',
  1781 				/* translators: 1: Parameter, 2: Minimum number. */
  2452 				/* translators: 1: Parameter, 2: Minimum number. */
  1782 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
  2453 				sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] )
  1783 			}
  2454 			);
  1784 		} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
  2455 		}
  1785 			if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
  2456 	}
       
  2457 
       
  2458 	if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
       
  2459 		if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
       
  2460 			return new WP_Error(
       
  2461 				'rest_out_of_bounds',
  1786 				/* translators: 1: Parameter, 2: Maximum number. */
  2462 				/* translators: 1: Parameter, 2: Maximum number. */
  1787 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
  2463 				sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] )
  1788 			} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
  2464 			);
       
  2465 		}
       
  2466 
       
  2467 		if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
       
  2468 			return new WP_Error(
       
  2469 				'rest_out_of_bounds',
  1789 				/* translators: 1: Parameter, 2: Maximum number. */
  2470 				/* translators: 1: Parameter, 2: Maximum number. */
  1790 				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
  2471 				sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] )
  1791 			}
  2472 			);
  1792 		} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
  2473 		}
  1793 			if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  2474 	}
  1794 				if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
  2475 
  1795 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  2476 	if ( isset( $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'] ) );
  2477 		if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  1797 				}
  2478 			if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
  1798 			} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
  2479 				return new WP_Error(
  1799 				if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
  2480 					'rest_out_of_bounds',
  1800 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  2481 					sprintf(
  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'] ) );
  2482 						/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  1802 				}
  2483 						__( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ),
  1803 			} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  2484 						$param,
  1804 				if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
  2485 						$args['minimum'],
  1805 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  2486 						$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'] ) );
  2487 					)
  1807 				}
  2488 				);
  1808 			} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  2489 			}
  1809 				if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
  2490 		}
  1810 					/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
  2491 
  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'] ) );
  2492 		if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
  1812 				}
  2493 			if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
  1813 			}
  2494 				return new WP_Error(
  1814 		}
  2495 					'rest_out_of_bounds',
       
  2496 					sprintf(
       
  2497 						/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
       
  2498 						__( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ),
       
  2499 						$param,
       
  2500 						$args['minimum'],
       
  2501 						$args['maximum']
       
  2502 					)
       
  2503 				);
       
  2504 			}
       
  2505 		}
       
  2506 
       
  2507 		if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) {
       
  2508 			if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
       
  2509 				return new WP_Error(
       
  2510 					'rest_out_of_bounds',
       
  2511 					sprintf(
       
  2512 						/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
       
  2513 						__( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ),
       
  2514 						$param,
       
  2515 						$args['minimum'],
       
  2516 						$args['maximum']
       
  2517 					)
       
  2518 				);
       
  2519 			}
       
  2520 		}
       
  2521 
       
  2522 		if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
       
  2523 			if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
       
  2524 				return new WP_Error(
       
  2525 					'rest_out_of_bounds',
       
  2526 					sprintf(
       
  2527 						/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
       
  2528 						__( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ),
       
  2529 						$param,
       
  2530 						$args['minimum'],
       
  2531 						$args['maximum']
       
  2532 					)
       
  2533 				);
       
  2534 			}
       
  2535 		}
       
  2536 	}
       
  2537 
       
  2538 	return true;
       
  2539 }
       
  2540 
       
  2541 /**
       
  2542  * Validates a string value based on a schema.
       
  2543  *
       
  2544  * @since 5.7.0
       
  2545  *
       
  2546  * @param mixed  $value The value to validate.
       
  2547  * @param array  $args  Schema array to use for validation.
       
  2548  * @param string $param The parameter name, used in error messages.
       
  2549  * @return true|WP_Error
       
  2550  */
       
  2551 function rest_validate_string_value_from_schema( $value, $args, $param ) {
       
  2552 	if ( ! is_string( $value ) ) {
       
  2553 		return new WP_Error(
       
  2554 			'rest_invalid_type',
       
  2555 			/* translators: 1: Parameter, 2: Type name. */
       
  2556 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
       
  2557 			array( 'param' => $param )
       
  2558 		);
       
  2559 	}
       
  2560 
       
  2561 	if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
       
  2562 		return new WP_Error(
       
  2563 			'rest_too_short',
       
  2564 			sprintf(
       
  2565 				/* translators: 1: Parameter, 2: Number of characters. */
       
  2566 				_n(
       
  2567 					'%1$s must be at least %2$s character long.',
       
  2568 					'%1$s must be at least %2$s characters long.',
       
  2569 					$args['minLength']
       
  2570 				),
       
  2571 				$param,
       
  2572 				number_format_i18n( $args['minLength'] )
       
  2573 			)
       
  2574 		);
       
  2575 	}
       
  2576 
       
  2577 	if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
       
  2578 		return new WP_Error(
       
  2579 			'rest_too_long',
       
  2580 			sprintf(
       
  2581 				/* translators: 1: Parameter, 2: Number of characters. */
       
  2582 				_n(
       
  2583 					'%1$s must be at most %2$s character long.',
       
  2584 					'%1$s must be at most %2$s characters long.',
       
  2585 					$args['maxLength']
       
  2586 				),
       
  2587 				$param,
       
  2588 				number_format_i18n( $args['maxLength'] )
       
  2589 			)
       
  2590 		);
       
  2591 	}
       
  2592 
       
  2593 	if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
       
  2594 		return new WP_Error(
       
  2595 			'rest_invalid_pattern',
       
  2596 			/* translators: 1: Parameter, 2: Pattern. */
       
  2597 			sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] )
       
  2598 		);
       
  2599 	}
       
  2600 
       
  2601 	return true;
       
  2602 }
       
  2603 
       
  2604 /**
       
  2605  * Validates an integer value based on a schema.
       
  2606  *
       
  2607  * @since 5.7.0
       
  2608  *
       
  2609  * @param mixed  $value The value to validate.
       
  2610  * @param array  $args  Schema array to use for validation.
       
  2611  * @param string $param The parameter name, used in error messages.
       
  2612  * @return true|WP_Error
       
  2613  */
       
  2614 function rest_validate_integer_value_from_schema( $value, $args, $param ) {
       
  2615 	$is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param );
       
  2616 	if ( is_wp_error( $is_valid_number ) ) {
       
  2617 		return $is_valid_number;
       
  2618 	}
       
  2619 
       
  2620 	if ( ! rest_is_integer( $value ) ) {
       
  2621 		return new WP_Error(
       
  2622 			'rest_invalid_type',
       
  2623 			/* translators: 1: Parameter, 2: Type name. */
       
  2624 			sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
       
  2625 			array( 'param' => $param )
       
  2626 		);
  1815 	}
  2627 	}
  1816 
  2628 
  1817 	return true;
  2629 	return true;
  1818 }
  2630 }
  1819 
  2631 
  1820 /**
  2632 /**
  1821  * Sanitize a value based on a schema.
  2633  * Sanitize a value based on a schema.
  1822  *
  2634  *
  1823  * @since 4.7.0
  2635  * @since 4.7.0
  1824  * @since 5.5.0 Added the `$param` parameter.
  2636  * @since 5.5.0 Added the `$param` parameter.
       
  2637  * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
  1825  *
  2638  *
  1826  * @param mixed  $value The value to sanitize.
  2639  * @param mixed  $value The value to sanitize.
  1827  * @param array  $args  Schema array to use for sanitization.
  2640  * @param array  $args  Schema array to use for sanitization.
  1828  * @param string $param The parameter name, used in error messages.
  2641  * @param string $param The parameter name, used in error messages.
  1829  * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
  2642  * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
  1830  */
  2643  */
  1831 function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
  2644 function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
       
  2645 	if ( isset( $args['anyOf'] ) ) {
       
  2646 		$matching_schema = rest_find_any_matching_schema( $value, $args, $param );
       
  2647 		if ( is_wp_error( $matching_schema ) ) {
       
  2648 			return $matching_schema;
       
  2649 		}
       
  2650 
       
  2651 		if ( ! isset( $args['type'] ) ) {
       
  2652 			$args['type'] = $matching_schema['type'];
       
  2653 		}
       
  2654 
       
  2655 		$value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
       
  2656 	}
       
  2657 
       
  2658 	if ( isset( $args['oneOf'] ) ) {
       
  2659 		$matching_schema = rest_find_one_matching_schema( $value, $args, $param );
       
  2660 		if ( is_wp_error( $matching_schema ) ) {
       
  2661 			return $matching_schema;
       
  2662 		}
       
  2663 
       
  2664 		if ( ! isset( $args['type'] ) ) {
       
  2665 			$args['type'] = $matching_schema['type'];
       
  2666 		}
       
  2667 
       
  2668 		$value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
       
  2669 	}
       
  2670 
  1832 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
  2671 	$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
  1833 
  2672 
  1834 	if ( ! isset( $args['type'] ) ) {
  2673 	if ( ! isset( $args['type'] ) ) {
  1835 		/* translators: 1. Parameter */
  2674 		/* translators: %s: Parameter. */
  1836 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
  2675 		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
  1837 	}
  2676 	}
  1838 
  2677 
  1839 	if ( is_array( $args['type'] ) ) {
  2678 	if ( is_array( $args['type'] ) ) {
  1840 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
  2679 		$best_type = rest_handle_multi_type_schema( $value, $args, $param );
  1847 	}
  2686 	}
  1848 
  2687 
  1849 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
  2688 	if ( ! in_array( $args['type'], $allowed_types, true ) ) {
  1850 		_doing_it_wrong(
  2689 		_doing_it_wrong(
  1851 			__FUNCTION__,
  2690 			__FUNCTION__,
  1852 			/* translators: 1. Parameter. 2. The list of allowed types. */
  2691 			/* 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 ),
  2692 			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'
  2693 			'5.5.0'
  1855 		);
  2694 		);
  1856 	}
  2695 	}
  1857 
  2696 
  1863 				$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  2702 				$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
  1864 			}
  2703 			}
  1865 		}
  2704 		}
  1866 
  2705 
  1867 		if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
  2706 		if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
  1868 			/* translators: 1: Parameter */
  2707 			/* translators: %s: Parameter. */
  1869 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
  2708 			return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
  1870 		}
  2709 		}
  1871 
  2710 
  1872 		return $value;
  2711 		return $value;
  1873 	}
  2712 	}
  1874 
  2713 
  1876 		$value = rest_sanitize_object( $value );
  2715 		$value = rest_sanitize_object( $value );
  1877 
  2716 
  1878 		foreach ( $value as $property => $v ) {
  2717 		foreach ( $value as $property => $v ) {
  1879 			if ( isset( $args['properties'][ $property ] ) ) {
  2718 			if ( isset( $args['properties'][ $property ] ) ) {
  1880 				$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  2719 				$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
  1881 			} elseif ( isset( $args['additionalProperties'] ) ) {
  2720 				continue;
       
  2721 			}
       
  2722 
       
  2723 			$pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
       
  2724 			if ( null !== $pattern_property_schema ) {
       
  2725 				$value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
       
  2726 				continue;
       
  2727 			}
       
  2728 
       
  2729 			if ( isset( $args['additionalProperties'] ) ) {
  1882 				if ( false === $args['additionalProperties'] ) {
  2730 				if ( false === $args['additionalProperties'] ) {
  1883 					unset( $value[ $property ] );
  2731 					unset( $value[ $property ] );
  1884 				} elseif ( is_array( $args['additionalProperties'] ) ) {
  2732 				} elseif ( is_array( $args['additionalProperties'] ) ) {
  1885 					$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
  2733 					$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
  1886 				}
  2734 				}
  1931 				return sanitize_text_field( $value );
  2779 				return sanitize_text_field( $value );
  1932 		}
  2780 		}
  1933 	}
  2781 	}
  1934 
  2782 
  1935 	if ( 'string' === $args['type'] ) {
  2783 	if ( 'string' === $args['type'] ) {
  1936 		return strval( $value );
  2784 		return (string) $value;
  1937 	}
  2785 	}
  1938 
  2786 
  1939 	return $value;
  2787 	return $value;
  1940 }
  2788 }
  1941 
  2789 
  1982 	}
  2830 	}
  1983 
  2831 
  1984 	$response = rest_do_request( $request );
  2832 	$response = rest_do_request( $request );
  1985 	if ( 200 === $response->status ) {
  2833 	if ( 200 === $response->status ) {
  1986 		$server = rest_get_server();
  2834 		$server = rest_get_server();
  1987 		$data   = (array) $response->get_data();
  2835 		$embed  = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false;
  1988 		$links  = $server::get_compact_response_links( $response );
  2836 		$data   = (array) $server->response_to_data( $response, $embed );
  1989 		if ( ! empty( $links ) ) {
       
  1990 			$data['_links'] = $links;
       
  1991 		}
       
  1992 
  2837 
  1993 		if ( 'OPTIONS' === $method ) {
  2838 		if ( 'OPTIONS' === $method ) {
  1994 			$response = rest_send_allow_header( $response, $server, $request );
  2839 			$response = rest_send_allow_header( $response, $server, $request );
  1995 
  2840 
  1996 			$memo[ $method ][ $path ] = array(
  2841 			$memo[ $method ][ $path ] = array(
  2032 
  2877 
  2033 /**
  2878 /**
  2034  * Filters the response to remove any fields not available in the given context.
  2879  * Filters the response to remove any fields not available in the given context.
  2035  *
  2880  *
  2036  * @since 5.5.0
  2881  * @since 5.5.0
       
  2882  * @since 5.6.0 Support the "patternProperties" keyword for objects.
       
  2883  *              Support the "anyOf" and "oneOf" keywords.
  2037  *
  2884  *
  2038  * @param array|object $data    The response data to modify.
  2885  * @param array|object $data    The response data to modify.
  2039  * @param array        $schema  The schema for the endpoint used to filter the response.
  2886  * @param array        $schema  The schema for the endpoint used to filter the response.
  2040  * @param string       $context The requested context.
  2887  * @param string       $context The requested context.
  2041  * @return array|object The filtered response data.
  2888  * @return array|object The filtered response data.
  2042  */
  2889  */
  2043 function rest_filter_response_by_context( $data, $schema, $context ) {
  2890 function rest_filter_response_by_context( $data, $schema, $context ) {
       
  2891 	if ( isset( $schema['anyOf'] ) ) {
       
  2892 		$matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
       
  2893 		if ( ! is_wp_error( $matching_schema ) ) {
       
  2894 			if ( ! isset( $schema['type'] ) ) {
       
  2895 				$schema['type'] = $matching_schema['type'];
       
  2896 			}
       
  2897 
       
  2898 			$data = rest_filter_response_by_context( $data, $matching_schema, $context );
       
  2899 		}
       
  2900 	}
       
  2901 
       
  2902 	if ( isset( $schema['oneOf'] ) ) {
       
  2903 		$matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
       
  2904 		if ( ! is_wp_error( $matching_schema ) ) {
       
  2905 			if ( ! isset( $schema['type'] ) ) {
       
  2906 				$schema['type'] = $matching_schema['type'];
       
  2907 			}
       
  2908 
       
  2909 			$data = rest_filter_response_by_context( $data, $matching_schema, $context );
       
  2910 		}
       
  2911 	}
       
  2912 
  2044 	if ( ! is_array( $data ) && ! is_object( $data ) ) {
  2913 	if ( ! is_array( $data ) && ! is_object( $data ) ) {
  2045 		return $data;
  2914 		return $data;
  2046 	}
  2915 	}
  2047 
  2916 
  2048 	if ( isset( $schema['type'] ) ) {
  2917 	if ( isset( $schema['type'] ) ) {
  2072 		if ( $is_array_type ) {
  2941 		if ( $is_array_type ) {
  2073 			$check = isset( $schema['items'] ) ? $schema['items'] : array();
  2942 			$check = isset( $schema['items'] ) ? $schema['items'] : array();
  2074 		} elseif ( $is_object_type ) {
  2943 		} elseif ( $is_object_type ) {
  2075 			if ( isset( $schema['properties'][ $key ] ) ) {
  2944 			if ( isset( $schema['properties'][ $key ] ) ) {
  2076 				$check = $schema['properties'][ $key ];
  2945 				$check = $schema['properties'][ $key ];
  2077 			} elseif ( $has_additional_properties ) {
  2946 			} else {
  2078 				$check = $schema['additionalProperties'];
  2947 				$pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
       
  2948 				if ( null !== $pattern_property_schema ) {
       
  2949 					$check = $pattern_property_schema;
       
  2950 				} elseif ( $has_additional_properties ) {
       
  2951 					$check = $schema['additionalProperties'];
       
  2952 				}
  2079 			}
  2953 			}
  2080 		}
  2954 		}
  2081 
  2955 
  2082 		if ( ! isset( $check['context'] ) ) {
  2956 		if ( ! isset( $check['context'] ) ) {
  2083 			continue;
  2957 			continue;
  2111 
  2985 
  2112 /**
  2986 /**
  2113  * Sets the "additionalProperties" to false by default for all object definitions in the schema.
  2987  * Sets the "additionalProperties" to false by default for all object definitions in the schema.
  2114  *
  2988  *
  2115  * @since 5.5.0
  2989  * @since 5.5.0
       
  2990  * @since 5.6.0 Support the "patternProperties" keyword.
  2116  *
  2991  *
  2117  * @param array $schema The schema to modify.
  2992  * @param array $schema The schema to modify.
  2118  * @return array The modified schema.
  2993  * @return array The modified schema.
  2119  */
  2994  */
  2120 function rest_default_additional_properties_to_false( $schema ) {
  2995 function rest_default_additional_properties_to_false( $schema ) {
  2122 
  2997 
  2123 	if ( in_array( 'object', $type, true ) ) {
  2998 	if ( in_array( 'object', $type, true ) ) {
  2124 		if ( isset( $schema['properties'] ) ) {
  2999 		if ( isset( $schema['properties'] ) ) {
  2125 			foreach ( $schema['properties'] as $key => $child_schema ) {
  3000 			foreach ( $schema['properties'] as $key => $child_schema ) {
  2126 				$schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
  3001 				$schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
       
  3002 			}
       
  3003 		}
       
  3004 
       
  3005 		if ( isset( $schema['patternProperties'] ) ) {
       
  3006 			foreach ( $schema['patternProperties'] as $key => $child_schema ) {
       
  3007 				$schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
  2127 			}
  3008 			}
  2128 		}
  3009 		}
  2129 
  3010 
  2130 		if ( ! isset( $schema['additionalProperties'] ) ) {
  3011 		if ( ! isset( $schema['additionalProperties'] ) ) {
  2131 			$schema['additionalProperties'] = false;
  3012 			$schema['additionalProperties'] = false;
  2212 	}
  3093 	}
  2213 
  3094 
  2214 	$route = '';
  3095 	$route = '';
  2215 
  3096 
  2216 	// The only controller that works is the Terms controller.
  3097 	// The only controller that works is the Terms controller.
  2217 	if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) {
  3098 	if ( $controller instanceof WP_REST_Terms_Controller ) {
  2218 		$namespace = 'wp/v2';
  3099 		$namespace = 'wp/v2';
  2219 		$rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
  3100 		$rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
  2220 		$route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
  3101 		$route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
  2221 	}
  3102 	}
  2222 
  3103 
  2256 	 *
  3137 	 *
  2257 	 * @param string $link The route with a leading slash, or an empty string.
  3138 	 * @param string $link The route with a leading slash, or an empty string.
  2258 	 */
  3139 	 */
  2259 	return apply_filters( 'rest_queried_resource_route', $route );
  3140 	return apply_filters( 'rest_queried_resource_route', $route );
  2260 }
  3141 }
       
  3142 
       
  3143 /**
       
  3144  * Retrieves an array of endpoint arguments from the item schema and endpoint method.
       
  3145  *
       
  3146  * @since 5.6.0
       
  3147  *
       
  3148  * @param array  $schema The full JSON schema for the endpoint.
       
  3149  * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
       
  3150  *                       checked for required values and may fall-back to a given default, this is not done
       
  3151  *                       on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
       
  3152  * @return array The endpoint arguments.
       
  3153  */
       
  3154 function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
       
  3155 
       
  3156 	$schema_properties       = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
       
  3157 	$endpoint_args           = array();
       
  3158 	$valid_schema_properties = rest_get_allowed_schema_keywords();
       
  3159 	$valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
       
  3160 
       
  3161 	foreach ( $schema_properties as $field_id => $params ) {
       
  3162 
       
  3163 		// Arguments specified as `readonly` are not allowed to be set.
       
  3164 		if ( ! empty( $params['readonly'] ) ) {
       
  3165 			continue;
       
  3166 		}
       
  3167 
       
  3168 		$endpoint_args[ $field_id ] = array(
       
  3169 			'validate_callback' => 'rest_validate_request_arg',
       
  3170 			'sanitize_callback' => 'rest_sanitize_request_arg',
       
  3171 		);
       
  3172 
       
  3173 		if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
       
  3174 			$endpoint_args[ $field_id ]['default'] = $params['default'];
       
  3175 		}
       
  3176 
       
  3177 		if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
       
  3178 			$endpoint_args[ $field_id ]['required'] = true;
       
  3179 		}
       
  3180 
       
  3181 		foreach ( $valid_schema_properties as $schema_prop ) {
       
  3182 			if ( isset( $params[ $schema_prop ] ) ) {
       
  3183 				$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
       
  3184 			}
       
  3185 		}
       
  3186 
       
  3187 		// Merge in any options provided by the schema property.
       
  3188 		if ( isset( $params['arg_options'] ) ) {
       
  3189 
       
  3190 			// Only use required / default from arg_options on CREATABLE endpoints.
       
  3191 			if ( WP_REST_Server::CREATABLE !== $method ) {
       
  3192 				$params['arg_options'] = array_diff_key(
       
  3193 					$params['arg_options'],
       
  3194 					array(
       
  3195 						'required' => '',
       
  3196 						'default'  => '',
       
  3197 					)
       
  3198 				);
       
  3199 			}
       
  3200 
       
  3201 			$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
       
  3202 		}
       
  3203 	}
       
  3204 
       
  3205 	return $endpoint_args;
       
  3206 }
       
  3207 
       
  3208 
       
  3209 /**
       
  3210  * Converts an error to a response object.
       
  3211  *
       
  3212  * This iterates over all error codes and messages to change it into a flat
       
  3213  * array. This enables simpler client behaviour, as it is represented as a
       
  3214  * list in JSON rather than an object/map.
       
  3215  *
       
  3216  * @since 5.7.0
       
  3217  *
       
  3218  * @param WP_Error $error WP_Error instance.
       
  3219  *
       
  3220  * @return WP_REST_Response List of associative arrays with code and message keys.
       
  3221  */
       
  3222 function rest_convert_error_to_response( $error ) {
       
  3223 	$status = array_reduce(
       
  3224 		$error->get_all_error_data(),
       
  3225 		function ( $status, $error_data ) {
       
  3226 			return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status;
       
  3227 		},
       
  3228 		500
       
  3229 	);
       
  3230 
       
  3231 	$errors = array();
       
  3232 
       
  3233 	foreach ( (array) $error->errors as $code => $messages ) {
       
  3234 		$all_data  = $error->get_all_error_data( $code );
       
  3235 		$last_data = array_pop( $all_data );
       
  3236 
       
  3237 		foreach ( (array) $messages as $message ) {
       
  3238 			$formatted = array(
       
  3239 				'code'    => $code,
       
  3240 				'message' => $message,
       
  3241 				'data'    => $last_data,
       
  3242 			);
       
  3243 
       
  3244 			if ( $all_data ) {
       
  3245 				$formatted['additional_data'] = $all_data;
       
  3246 			}
       
  3247 
       
  3248 			$errors[] = $formatted;
       
  3249 		}
       
  3250 	}
       
  3251 
       
  3252 	$data = $errors[0];
       
  3253 	if ( count( $errors ) > 1 ) {
       
  3254 		// Remove the primary error.
       
  3255 		array_shift( $errors );
       
  3256 		$data['additional_errors'] = $errors;
       
  3257 	}
       
  3258 
       
  3259 	return new WP_REST_Response( $data, $status );
       
  3260 }