WordPress Version: 5.6
/**
* Validate a value based on a schema.
*
* @since 4.7.0
* @since 4.9.0 Support the "object" type.
* @since 5.2.0 Support validating "additionalProperties" against a schema.
* @since 5.3.0 Support multiple types.
* @since 5.4.0 Convert an empty string to an empty object.
* @since 5.5.0 Add the "uuid" and "hex-color" formats.
* Support the "minLength", "maxLength" and "pattern" keywords for strings.
* Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
* Validate required properties.
* @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
* Support the "multipleOf" keyword for numbers and integers.
* Support the "patternProperties" keyword for objects.
* Support the "anyOf" and "oneOf" keywords.
*
* @param mixed $value The value to validate.
* @param array $args Schema array to use for validation.
* @param string $param The parameter name, used in error messages.
* @return true|WP_Error
*/
function rest_validate_value_from_schema($value, $args, $param = '')
{
if (isset($args['anyOf'])) {
$matching_schema = rest_find_any_matching_schema($value, $args, $param);
if (is_wp_error($matching_schema)) {
return $matching_schema;
}
if (!isset($args['type']) && isset($matching_schema['type'])) {
$args['type'] = $matching_schema['type'];
}
}
if (isset($args['oneOf'])) {
$matching_schema = rest_find_one_matching_schema($value, $args, $param);
if (is_wp_error($matching_schema)) {
return $matching_schema;
}
if (!isset($args['type']) && isset($matching_schema['type'])) {
$args['type'] = $matching_schema['type'];
}
}
$allowed_types = array('array', 'object', 'string', 'number', 'integer', 'boolean', 'null');
if (!isset($args['type'])) {
/* translators: %s: Parameter. */
_doing_it_wrong(__FUNCTION__, sprintf(__('The "type" schema keyword for %s is required.'), $param), '5.5.0');
}
if (is_array($args['type'])) {
$best_type = rest_handle_multi_type_schema($value, $args, $param);
if (!$best_type) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: List of types. */
sprintf(__('%1$s is not of type %2$s.'), $param, implode(',', $args['type'])),
array('param' => $param)
);
}
$args['type'] = $best_type;
}
if (!in_array($args['type'], $allowed_types, true)) {
_doing_it_wrong(
__FUNCTION__,
/* translators: 1: Parameter, 2: The list of allowed types. */
wp_sprintf(__('The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.'), $param, $allowed_types),
'5.5.0'
);
}
if ('array' === $args['type']) {
if (!rest_is_array($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'array'),
array('param' => $param)
);
}
$value = rest_sanitize_array($value);
if (isset($args['items'])) {
foreach ($value as $index => $v) {
$is_valid = rest_validate_value_from_schema($v, $args['items'], $param . '[' . $index . ']');
if (is_wp_error($is_valid)) {
return $is_valid;
}
}
}
if (isset($args['minItems']) && count($value) < $args['minItems']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number. */
_n('%1$s must contain at least %2$s item.', '%1$s must contain at least %2$s items.', $args['minItems']),
$param,
number_format_i18n($args['minItems'])
));
}
if (isset($args['maxItems']) && count($value) > $args['maxItems']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number. */
_n('%1$s must contain at most %2$s item.', '%1$s must contain at most %2$s items.', $args['maxItems']),
$param,
number_format_i18n($args['maxItems'])
));
}
if (!empty($args['uniqueItems']) && !rest_validate_array_contains_unique_items($value)) {
/* translators: 1: Parameter. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s has duplicate items.'), $param));
}
}
if ('object' === $args['type']) {
if (!rest_is_object($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'object'),
array('param' => $param)
);
}
$value = rest_sanitize_object($value);
if (isset($args['required']) && is_array($args['required'])) {
// schema version 4
foreach ($args['required'] as $name) {
if (!array_key_exists($name, $value)) {
/* translators: 1: Property of an object, 2: Parameter. */
return new WP_Error('rest_property_required', sprintf(__('%1$s is a required property of %2$s.'), $name, $param));
}
}
} elseif (isset($args['properties'])) {
// schema version 3
foreach ($args['properties'] as $name => $property) {
if (isset($property['required']) && true === $property['required'] && !array_key_exists($name, $value)) {
/* translators: 1: Property of an object, 2: Parameter. */
return new WP_Error('rest_property_required', sprintf(__('%1$s is a required property of %2$s.'), $name, $param));
}
}
}
foreach ($value as $property => $v) {
if (isset($args['properties'][$property])) {
$is_valid = rest_validate_value_from_schema($v, $args['properties'][$property], $param . '[' . $property . ']');
if (is_wp_error($is_valid)) {
return $is_valid;
}
continue;
}
$pattern_property_schema = rest_find_matching_pattern_property_schema($property, $args);
if (null !== $pattern_property_schema) {
$is_valid = rest_validate_value_from_schema($v, $pattern_property_schema, $param . '[' . $property . ']');
if (is_wp_error($is_valid)) {
return $is_valid;
}
continue;
}
if (isset($args['additionalProperties'])) {
if (false === $args['additionalProperties']) {
/* translators: %s: Property of an object. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s is not a valid property of Object.'), $property));
}
if (is_array($args['additionalProperties'])) {
$is_valid = rest_validate_value_from_schema($v, $args['additionalProperties'], $param . '[' . $property . ']');
if (is_wp_error($is_valid)) {
return $is_valid;
}
}
}
}
if (isset($args['minProperties']) && count($value) < $args['minProperties']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number. */
_n('%1$s must contain at least %2$s property.', '%1$s must contain at least %2$s properties.', $args['minProperties']),
$param,
number_format_i18n($args['minProperties'])
));
}
if (isset($args['maxProperties']) && count($value) > $args['maxProperties']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number. */
_n('%1$s must contain at most %2$s property.', '%1$s must contain at most %2$s properties.', $args['maxProperties']),
$param,
number_format_i18n($args['maxProperties'])
));
}
}
if ('null' === $args['type']) {
if (null !== $value) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'null'),
array('param' => $param)
);
}
return true;
}
if (!empty($args['enum'])) {
if (!in_array($value, $args['enum'], true)) {
/* translators: 1: Parameter, 2: List of valid values. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s is not one of %2$s.'), $param, implode(', ', $args['enum'])));
}
}
if (in_array($args['type'], array('integer', 'number'), true)) {
if (!is_numeric($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, $args['type']),
array('param' => $param)
);
}
if (isset($args['multipleOf']) && fmod($value, $args['multipleOf']) !== 0.0) {
/* translators: 1: Parameter, 2: Multiplier. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s must be a multiple of %2$s.'), $param, $args['multipleOf']));
}
}
if ('integer' === $args['type'] && !rest_is_integer($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'integer'),
array('param' => $param)
);
}
if ('boolean' === $args['type'] && !rest_is_boolean($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'boolean'),
array('param' => $param)
);
}
if ('string' === $args['type']) {
if (!is_string($value)) {
return new WP_Error(
'rest_invalid_type',
/* translators: 1: Parameter, 2: Type name. */
sprintf(__('%1$s is not of type %2$s.'), $param, 'string'),
array('param' => $param)
);
}
if (isset($args['minLength']) && mb_strlen($value) < $args['minLength']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number of characters. */
_n('%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength']),
$param,
number_format_i18n($args['minLength'])
));
}
if (isset($args['maxLength']) && mb_strlen($value) > $args['maxLength']) {
return new WP_Error('rest_invalid_param', sprintf(
/* translators: 1: Parameter, 2: Number of characters. */
_n('%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength']),
$param,
number_format_i18n($args['maxLength'])
));
}
if (isset($args['pattern']) && !rest_validate_json_schema_pattern($args['pattern'], $value)) {
/* translators: 1: Parameter, 2: Pattern. */
return new WP_Error('rest_invalid_pattern', sprintf(__('%1$s does not match pattern %2$s.'), $param, $args['pattern']));
}
}
// The "format" keyword should only be applied to strings. However, for backward compatibility,
// we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
if (isset($args['format']) && (!isset($args['type']) || 'string' === $args['type'] || !in_array($args['type'], $allowed_types, true))) {
switch ($args['format']) {
case 'hex-color':
if (!rest_parse_hex_color($value)) {
return new WP_Error('rest_invalid_hex_color', __('Invalid hex color.'));
}
break;
case 'date-time':
if (!rest_parse_date($value)) {
return new WP_Error('rest_invalid_date', __('Invalid date.'));
}
break;
case 'email':
if (!is_email($value)) {
return new WP_Error('rest_invalid_email', __('Invalid email address.'));
}
break;
case 'ip':
if (!rest_is_ip_address($value)) {
/* translators: %s: IP address. */
return new WP_Error('rest_invalid_param', sprintf(__('%s is not a valid IP address.'), $param));
}
break;
case 'uuid':
if (!wp_is_uuid($value)) {
/* translators: %s: The name of a JSON field expecting a valid UUID. */
return new WP_Error('rest_invalid_uuid', sprintf(__('%s is not a valid UUID.'), $param));
}
break;
}
}
if (in_array($args['type'], array('number', 'integer'), true) && (isset($args['minimum']) || isset($args['maximum']))) {
if (isset($args['minimum']) && !isset($args['maximum'])) {
if (!empty($args['exclusiveMinimum']) && $value <= $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s must be greater than %2$d'), $param, $args['minimum']));
} elseif (empty($args['exclusiveMinimum']) && $value < $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s must be greater than or equal to %2$d'), $param, $args['minimum']));
}
} elseif (isset($args['maximum']) && !isset($args['minimum'])) {
if (!empty($args['exclusiveMaximum']) && $value >= $args['maximum']) {
/* translators: 1: Parameter, 2: Maximum number. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s must be less than %2$d'), $param, $args['maximum']));
} elseif (empty($args['exclusiveMaximum']) && $value > $args['maximum']) {
/* translators: 1: Parameter, 2: Maximum number. */
return new WP_Error('rest_invalid_param', sprintf(__('%1$s must be less than or equal to %2$d'), $param, $args['maximum']));
}
} elseif (isset($args['maximum']) && isset($args['minimum'])) {
if (!empty($args['exclusiveMinimum']) && !empty($args['exclusiveMaximum'])) {
if ($value >= $args['maximum'] || $value <= $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
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']));
}
} elseif (empty($args['exclusiveMinimum']) && !empty($args['exclusiveMaximum'])) {
if ($value >= $args['maximum'] || $value < $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
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']));
}
} elseif (!empty($args['exclusiveMinimum']) && empty($args['exclusiveMaximum'])) {
if ($value > $args['maximum'] || $value <= $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
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']));
}
} elseif (empty($args['exclusiveMinimum']) && empty($args['exclusiveMaximum'])) {
if ($value > $args['maximum'] || $value < $args['minimum']) {
/* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
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']));
}
}
}
}
return true;
}