<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[48306] trunk: REST API: Make multi-typed schemas more robust.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/48306">48306</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/48306","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>TimothyBlynJacobs</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-07-05 00:13:37 +0000 (Sun, 05 Jul 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>REST API: Make multi-typed schemas more robust.

A multi-type schema is a schema where the `type` keyword is an array of possible types instead of a single type. For instance, `[ 'object', 'string' ]` would allow objects or string values.

In <a href="https://core.trac.wordpress.org/changeset/46249">[46249]</a> basic support for these schemas was introduced. The validator would loop over each schema type trying to find a version that matched. This worked for valid values, but for invalid values it provided unhelpful error messages. The sanitizer also had its utility restricted.

In this commit, the validators and sanitizers will first determine the best type of the passed value and then apply the schema with that set type. In the case that a value could match multiple types, the schema of the first matching type will be used.

To maintain backward compatibility, if unsupported schema types are used, the value will always pass validation. A doing it wrong notice is issued in this case.

Fixes <a href="https://core.trac.wordpress.org/ticket/50300">#50300</a>.
Props pentatonicfunk, dlh, TimothyBlynJacobs.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesrestapiphp">trunk/src/wp-includes/rest-api.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemasanitizationphp">trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemavalidationphp">trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiphp">trunk/tests/phpunit/tests/rest-api.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api.php        2020-07-04 22:01:08 UTC (rev 48305)
+++ trunk/src/wp-includes/rest-api.php  2020-07-05 00:13:37 UTC (rev 48306)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -986,6 +986,50 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Retrieves the avatar urls in various sizes.
+ *
+ * @since 4.7.0
+ *
+ * @see get_avatar_url()
+ *
+ * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
+ *                           user email, WP_User object, WP_Post object, or WP_Comment object.
+ * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
+ */
+function rest_get_avatar_urls( $id_or_email ) {
+       $avatar_sizes = rest_get_avatar_sizes();
+
+       $urls = array();
+       foreach ( $avatar_sizes as $size ) {
+               $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
+       }
+
+       return $urls;
+}
+
+/**
+ * Retrieves the pixel sizes for avatars.
+ *
+ * @since 4.7.0
+ *
+ * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
+ */
+function rest_get_avatar_sizes() {
+       /**
+        * Filters the REST avatar sizes.
+        *
+        * Use this filter to adjust the array of sizes returned by the
+        * `rest_get_avatar_sizes` function.
+        *
+        * @since 4.4.0
+        *
+        * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
+        *                     Default `[ 24, 48, 96 ]`.
+        */
+       return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Parses an RFC3339 time into a Unix timestamp.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1116,7 +1160,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px">        $args = $attributes['args'][ $param ];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return rest_sanitize_value_from_schema( $value, $args );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return rest_sanitize_value_from_schema( $value, $args, $param );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1219,50 +1263,182 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Retrieves the avatar urls in various sizes.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determines if a given value is integer-like.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 4.7.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @see get_avatar_url()
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param mixed $maybe_integer The value being evaluated.
+ * @return bool True if an integer, otherwise false.
+ */
+function rest_is_integer( $maybe_integer ) {
+       return round( floatval( $maybe_integer ) ) === floatval( $maybe_integer );
+}
+
+/**
+ * Determines if a given value is array-like.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
- *                           user email, WP_User object, WP_Post object, or WP_Comment object.
- * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0
+ *
+ * @param mixed $maybe_array The value being evaluated.
+ * @return bool
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function rest_get_avatar_urls( $id_or_email ) {
-       $avatar_sizes = rest_get_avatar_sizes();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function rest_is_array( $maybe_array ) {
+       if ( is_scalar( $maybe_array ) ) {
+               $maybe_array = wp_parse_list( $maybe_array );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $urls = array();
-       foreach ( $avatar_sizes as $size ) {
-               $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return wp_is_numeric_array( $maybe_array );
+}
+
+/**
+ * Converts an array-like value to an array.
+ *
+ * @since 5.5.0
+ *
+ * @param mixed $maybe_array The value being evaluated.
+ * @return array Returns the array extracted from the value.
+ */
+function rest_sanitize_array( $maybe_array ) {
+       if ( is_scalar( $maybe_array ) ) {
+               return wp_parse_list( $maybe_array );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return $urls;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! is_array( $maybe_array ) ) {
+               return array();
+       }
+
+       // Normalize to numeric array so nothing unexpected is in the keys.
+       return array_values( $maybe_array );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Retrieves the pixel sizes for avatars.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determines if a given value is object-like.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 4.7.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param mixed $maybe_object The value being evaluated.
+ * @return bool True if object like, otherwise false.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function rest_get_avatar_sizes() {
-       /**
-        * Filters the REST avatar sizes.
-        *
-        * Use this filter to adjust the array of sizes returned by the
-        * `rest_get_avatar_sizes` function.
-        *
-        * @since 4.4.0
-        *
-        * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
-        *                     Default `[ 24, 48, 96 ]`.
-        */
-       return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function rest_is_object( $maybe_object ) {
+       if ( '' === $maybe_object ) {
+               return true;
+       }
+
+       if ( $maybe_object instanceof stdClass ) {
+               return true;
+       }
+
+       if ( $maybe_object instanceof JsonSerializable ) {
+               $maybe_object = $maybe_object->jsonSerialize();
+       }
+
+       return is_array( $maybe_object );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Converts an object-like value to an object.
+ *
+ * @since 5.5.0
+ *
+ * @param mixed $maybe_object The value being evaluated.
+ * @return array Returns the object extracted from the value.
+ */
+function rest_sanitize_object( $maybe_object ) {
+       if ( '' === $maybe_object ) {
+               return array();
+       }
+
+       if ( $maybe_object instanceof stdClass ) {
+               return (array) $maybe_object;
+       }
+
+       if ( $maybe_object instanceof JsonSerializable ) {
+               $maybe_object = $maybe_object->jsonSerialize();
+       }
+
+       if ( ! is_array( $maybe_object ) ) {
+               return array();
+       }
+
+       return $maybe_object;
+}
+
+/**
+ * Gets the best type for a value.
+ *
+ * @since 5.5.0
+ *
+ * @param mixed $value The value to check.
+ * @param array $types The list of possible types.
+ * @return string The best matching type, an empty string if no types match.
+ */
+function rest_get_best_type_for_value( $value, $types ) {
+       static $checks = array(
+               'array'   => 'rest_is_array',
+               'object'  => 'rest_is_object',
+               'integer' => 'rest_is_integer',
+               'number'  => 'is_numeric',
+               'boolean' => 'rest_is_boolean',
+               'string'  => 'is_string',
+               'null'    => 'is_null',
+       );
+
+       // Both arrays and objects allow empty strings to be converted to their types.
+       // But the best answer for this type is a string.
+       if ( '' === $value && in_array( 'string', $types, true ) ) {
+               return 'string';
+       }
+
+       foreach ( $types as $type ) {
+               if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
+                       return $type;
+               }
+       }
+
+       return '';
+}
+
+/**
+ * Handles getting the best type for a multi-type schema.
+ *
+ * This is a wrapper for {@see rest_get_best_type_for_value()} that handles
+ * backward compatibility for schemas that use invalid types.
+ *
+ * @since 5.5.0
+ *
+ * @param mixed  $value The value to check.
+ * @param array  $args  The schema array to use.
+ * @param string $param The parameter name, used in error messages.
+ * @return string
+ */
+function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
+       $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
+       $invalid_types = array_diff( $args['type'], $allowed_types );
+
+       if ( $invalid_types ) {
+               _doing_it_wrong(
+                       __FUNCTION__,
+                       /* translators: 1. Parameter. 2. List of allowed types. */
+                       wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
+                       '5.5.0'
+               );
+       }
+
+       $best_type = rest_get_best_type_for_value( $value, $args['type'] );
+
+       if ( ! $best_type ) {
+               if ( ! $invalid_types ) {
+                       return '';
+               }
+
+               // Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
+               $best_type = reset( $invalid_types );
+       }
+
+       return $best_type;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Validate a value based on a schema.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.7.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1284,42 +1460,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">        $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! isset( $args['type'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                _doing_it_wrong( __FUNCTION__, __( 'The "type" schema keyword is required.' ), '5.5.0' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /* translators: 1. Parameter */
+               _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( is_array( $args['type'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( $args['type'] as $type ) {
-                       $type_args         = $args;
-                       $type_args['type'] = $type;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $best_type = rest_handle_multi_type_schema( $value, $args, $param );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( true === rest_validate_value_from_schema( $value, $type_args, $param ) ) {
-                               return true;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $best_type ) {
+                       /* translators: 1: Parameter, 2: List of types. */
+                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /* translators: 1: Parameter, 2: List of types. */
-               return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $args['type'] = $best_type;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! in_array( $args['type'], $allowed_types, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                _doing_it_wrong(
</span><span class="cx" style="display: block; padding: 0 10px">                        __FUNCTION__,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1. The list of allowed types. */
-                       wp_sprintf( __( 'The "type" schema keyword can only be on of the built-in types: %l.' ), $allowed_types ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /* translators: 1. Parameter 2. The list of allowed types. */
+                       wp_sprintf( __( 'The "type" schema keyword for %1$s can only be on of the built-in types: %2$l.' ), $param, $allowed_types ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         '5.5.0'
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'array' === $args['type'] ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! is_null( $value ) ) {
-                       $value = wp_parse_list( $value );
-               }
-
-               if ( ! wp_is_numeric_array( $value ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! rest_is_array( $value ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         /* translators: 1: Parameter, 2: Type name. */
</span><span class="cx" style="display: block; padding: 0 10px">                        return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $value = rest_sanitize_array( $value );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $value as $index => $v ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( is_wp_error( $is_valid ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1339,23 +1511,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'object' === $args['type'] ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( '' === $value ) {
-                       $value = array();
-               }
-
-               if ( $value instanceof stdClass ) {
-                       $value = (array) $value;
-               }
-
-               if ( $value instanceof JsonSerializable ) {
-                       $value = $value->jsonSerialize();
-               }
-
-               if ( ! is_array( $value ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! rest_is_object( $value ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         /* translators: 1: Parameter, 2: Type name. */
</span><span class="cx" style="display: block; padding: 0 10px">                        return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $value = rest_sanitize_object( $value );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
</span><span class="cx" style="display: block; padding: 0 10px">                        foreach ( $args['required'] as $name ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( ! array_key_exists( $name, $value ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1415,7 +1577,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 /* translators: 1: Parameter, 2: Type name. */
</span><span class="cx" style="display: block; padding: 0 10px">                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1551,85 +1713,65 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Sanitize a value based on a schema.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.7.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.5.0 Added the `$param` parameter.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param mixed $value The value to sanitize.
- * @param array $args  Schema array to use for sanitization.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param mixed  $value The value to sanitize.
+ * @param array  $args  Schema array to use for sanitization.
+ * @param string $param The parameter name, used in error messages.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return true|WP_Error
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function rest_sanitize_value_from_schema( $value, $args ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! isset( $args['type'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                _doing_it_wrong( __FUNCTION__, __( 'The "type" schema keyword is required.' ), '5.5.0' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /* translators: 1. Parameter */
+               _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( is_array( $args['type'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Determine which type the value was validated against,
-               // and use that type when performing sanitization.
-               $validated_type = '';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $best_type = rest_handle_multi_type_schema( $value, $args, $param );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( $args['type'] as $type ) {
-                       $type_args         = $args;
-                       $type_args['type'] = $type;
-
-                       if ( ! is_wp_error( rest_validate_value_from_schema( $value, $type_args ) ) ) {
-                               $validated_type = $type;
-                               break;
-                       }
-               }
-
-               if ( ! $validated_type ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $best_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         return null;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $args['type'] = $validated_type;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $args['type'] = $best_type;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! in_array( $args['type'], $allowed_types, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                _doing_it_wrong(
</span><span class="cx" style="display: block; padding: 0 10px">                        __FUNCTION__,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1. The list of allowed types. */
-                       wp_sprintf( __( 'The "type" schema keyword can only be on of the built-in types: %l.' ), $allowed_types ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /* translators: 1. Parameter. 2. The list of allowed types. */
+                       wp_sprintf( __( 'The "type" schema keyword for %1$s can only be on of the built-in types: %2$l.' ), $param, $allowed_types ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         '5.5.0'
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'array' === $args['type'] ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $value = rest_sanitize_array( $value );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( empty( $args['items'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return (array) $value;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return $value;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $value = wp_parse_list( $value );
</del><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $value as $index => $v ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Normalize to numeric array so nothing unexpected is in the keys.
-               $value = array_values( $value );
</del><span class="cx" style="display: block; padding: 0 10px">                 return $value;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'object' === $args['type'] ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $value instanceof stdClass ) {
-                       $value = (array) $value;
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $value = rest_sanitize_object( $value );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $value instanceof JsonSerializable ) {
-                       $value = $value->jsonSerialize();
-               }
-
-               if ( ! is_array( $value ) ) {
-                       return array();
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $value as $property => $v ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( isset( $args['properties'][ $property ] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
</ins><span class="cx" style="display: block; padding: 0 10px">                         } elseif ( isset( $args['additionalProperties'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( false === $args['additionalProperties'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        unset( $value[ $property ] );
</span><span class="cx" style="display: block; padding: 0 10px">                                } elseif ( is_array( $args['additionalProperties'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestschemasanitizationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php   2020-07-04 22:01:08 UTC (rev 48305)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php     2020-07-05 00:13:37 UTC (rev 48306)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -341,7 +341,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNull( rest_sanitize_value_from_schema( null, $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( '2019-09-19T18:00:00', rest_sanitize_value_from_schema( '2019-09-19T18:00:00', $schema ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNull( rest_sanitize_value_from_schema( 'lalala', $schema ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( 'lalala', rest_sanitize_value_from_schema( 'lalala', $schema ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -394,7 +394,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 'raw' => 'My Value' ), rest_sanitize_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( array( 'raw' => '1' ), rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_object_or_bool() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -423,6 +423,45 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => '0' ), $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => 0 ), $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 50300
+        */
+       public function test_multi_type_with_no_known_types() {
+               $this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
+               $this->setExpectedIncorrectUsage( 'rest_sanitize_value_from_schema' );
+
+               $schema = array(
+                       'type' => array( 'invalid', 'type' ),
+               );
+
+               $this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
+       }
+
+       /**
+        * @ticket 50300
+        */
+       public function test_multi_type_with_some_unknown_types() {
+               $this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
+               $this->setExpectedIncorrectUsage( 'rest_sanitize_value_from_schema' );
+
+               $schema = array(
+                       'type' => array( 'object', 'type' ),
+               );
+
+               $this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
+       }
+
+       /**
+        * @ticket 50300
+        */
+       public function test_multi_type_returns_null_if_no_valid_type() {
+               $schema = array(
+                       'type' => array( 'number', 'string' ),
+               );
+
+               $this->assertNull( rest_sanitize_value_from_schema( array( 'Hello!' ), $schema ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestschemavalidationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php     2020-07-04 22:01:08 UTC (rev 48305)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php       2020-07-05 00:13:37 UTC (rev 48306)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -387,7 +387,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( '2019-09-19T18:00:00', $schema ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertWPError( rest_validate_value_from_schema( 'some random string', $schema ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               $error = rest_validate_value_from_schema( 'some random string', $schema );
+               $this->assertWPError( $error );
+               $this->assertEquals( 'Invalid date.', $error->get_error_message() );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_object_or_string() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -402,10 +405,60 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertWPError( rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               $error = rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema );
+               $this->assertWPError( $error );
+               $this->assertEquals( '[raw] is not of type string.', $error->get_error_message() );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 50300
+        */
+       public function test_null_or_integer() {
+               $schema = array(
+                       'type'    => array( 'null', 'integer' ),
+                       'minimum' => 10,
+                       'maximum' => 20,
+               );
+
+               $this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
+               $this->assertTrue( rest_validate_value_from_schema( 15, $schema ) );
+               $this->assertTrue( rest_validate_value_from_schema( '15', $schema ) );
+
+               $error = rest_validate_value_from_schema( 30, $schema, 'param' );
+               $this->assertWPError( $error );
+               $this->assertEquals( 'param must be between 10 (inclusive) and 20 (inclusive)', $error->get_error_message() );
+       }
+
+       /**
+        * @ticket 50300
+        */
+       public function test_multi_type_with_no_known_types() {
+               $this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
+               $this->setExpectedIncorrectUsage( 'rest_validate_value_from_schema' );
+
+               $schema = array(
+                       'type' => array( 'invalid', 'type' ),
+               );
+
+               $this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
+       }
+
+       /**
+        * @ticket 50300
+        */
+       public function test_multi_type_with_some_unknown_types() {
+               $this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
+               $this->setExpectedIncorrectUsage( 'rest_validate_value_from_schema' );
+
+               $schema = array(
+                       'type' => array( 'object', 'type' ),
+               );
+
+               $this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @ticket 48820
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_string_min_length() {
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api.php    2020-07-04 22:01:08 UTC (rev 48305)
+++ trunk/tests/phpunit/tests/rest-api.php      2020-07-05 00:13:37 UTC (rev 48306)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -8,6 +8,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> require_once ABSPATH . 'wp-admin/includes/admin.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require_once ABSPATH . WPINC . '/rest-api.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require_once __DIR__ . '/../includes/class-jsonserializable-object.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * @group restapi
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1457,4 +1458,359 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $term = self::factory()->term->create_and_get();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term->term_id ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @dataProvider _dp_rest_is_object
+        * @ticket 50300
+        * @param bool  $expected Expected result of the check.
+        * @param mixed $value    The value to check.
+        */
+       public function test_rest_is_object( $expected, $value ) {
+               $is_object = rest_is_object( $value );
+
+               if ( $expected ) {
+                       $this->assertTrue( $is_object );
+               } else {
+                       $this->assertFalse( $is_object );
+               }
+       }
+
+       public function _dp_rest_is_object() {
+               return array(
+                       array(
+                               true,
+                               '',
+                       ),
+                       array(
+                               true,
+                               new stdClass(),
+                       ),
+                       array(
+                               true,
+                               new JsonSerializable_Object( array( 'hi' => 'there' ) ),
+                       ),
+                       array(
+                               true,
+                               array( 'hi' => 'there' ),
+                       ),
+                       array(
+                               true,
+                               array(),
+                       ),
+                       array(
+                               true,
+                               array( 'a', 'b' ),
+                       ),
+                       array(
+                               false,
+                               new Basic_Object(),
+                       ),
+                       array(
+                               false,
+                               new JsonSerializable_Object( 'str' ),
+                       ),
+                       array(
+                               false,
+                               'str',
+                       ),
+                       array(
+                               false,
+                               5,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider _dp_rest_sanitize_object
+        * @ticket       50300
+        * @param array $expected Expected sanitized version.
+        * @param mixed $value    The value to sanitize.
+        */
+       public function test_rest_sanitize_object( $expected, $value ) {
+               $sanitized = rest_sanitize_object( $value );
+               $this->assertEquals( $expected, $sanitized );
+       }
+
+       public function _dp_rest_sanitize_object() {
+               return array(
+                       array(
+                               array(),
+                               '',
+                       ),
+                       array(
+                               array( 'a' => '1' ),
+                               (object) array( 'a' => '1' ),
+                       ),
+                       array(
+                               array( 'hi' => 'there' ),
+                               new JsonSerializable_Object( array( 'hi' => 'there' ) ),
+                       ),
+                       array(
+                               array( 'hi' => 'there' ),
+                               array( 'hi' => 'there' ),
+                       ),
+                       array(
+                               array(),
+                               array(),
+                       ),
+                       array(
+                               array( 'a', 'b' ),
+                               array( 'a', 'b' ),
+                       ),
+                       array(
+                               array(),
+                               new Basic_Object(),
+                       ),
+                       array(
+                               array(),
+                               new JsonSerializable_Object( 'str' ),
+                       ),
+                       array(
+                               array(),
+                               'str',
+                       ),
+                       array(
+                               array(),
+                               5,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider _dp_rest_is_array
+        * @ticket 50300
+        * @param bool  $expected Expected result of the check.
+        * @param mixed $value    The value to check.
+        */
+       public function test_rest_is_array( $expected, $value ) {
+               $is_array = rest_is_array( $value );
+
+               if ( $expected ) {
+                       $this->assertTrue( $is_array );
+               } else {
+                       $this->assertFalse( $is_array );
+               }
+       }
+
+       public function _dp_rest_is_array() {
+               return array(
+                       array(
+                               true,
+                               '',
+                       ),
+                       array(
+                               true,
+                               array( 'a', 'b' ),
+                       ),
+                       array(
+                               true,
+                               array(),
+                       ),
+                       array(
+                               true,
+                               'a,b,c',
+                       ),
+                       array(
+                               true,
+                               'a',
+                       ),
+                       array(
+                               true,
+                               5,
+                       ),
+                       array(
+                               false,
+                               new stdClass(),
+                       ),
+                       array(
+                               false,
+                               new JsonSerializable_Object( array( 'hi' => 'there' ) ),
+                       ),
+                       array(
+                               false,
+                               array( 'hi' => 'there' ),
+                       ),
+                       array(
+                               false,
+                               new Basic_Object(),
+                       ),
+                       array(
+                               false,
+                               new JsonSerializable_Object( 'str' ),
+                       ),
+                       array(
+                               false,
+                               null,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider _dp_rest_sanitize_array
+        * @ticket       50300
+        * @param array $expected Expected sanitized version.
+        * @param mixed $value    The value to sanitize.
+        */
+       public function test_rest_sanitize_array( $expected, $value ) {
+               $sanitized = rest_sanitize_array( $value );
+               $this->assertEquals( $expected, $sanitized );
+       }
+
+       public function _dp_rest_sanitize_array() {
+               return array(
+                       array(
+                               array(),
+                               '',
+                       ),
+                       array(
+                               array( 'a', 'b' ),
+                               array( 'a', 'b' ),
+                       ),
+                       array(
+                               array(),
+                               array(),
+                       ),
+                       array(
+                               array( 'a', 'b', 'c' ),
+                               'a,b,c',
+                       ),
+                       array(
+                               array( 'a' ),
+                               'a',
+                       ),
+                       array(
+                               array( 'a', 'b' ),
+                               'a,b,',
+                       ),
+                       array(
+                               array( '5' ),
+                               5,
+                       ),
+                       array(
+                               array(),
+                               new stdClass(),
+                       ),
+                       array(
+                               array(),
+                               new JsonSerializable_Object( array( 'hi' => 'there' ) ),
+                       ),
+                       array(
+                               array( 'there' ),
+                               array( 'hi' => 'there' ),
+                       ),
+                       array(
+                               array(),
+                               new Basic_Object(),
+                       ),
+                       array(
+                               array(),
+                               new JsonSerializable_Object( 'str' ),
+                       ),
+                       array(
+                               array(),
+                               null,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider _dp_get_best_type_for_value
+        * @ticket 50300
+        * @param string $expected The expected best type.
+        * @param mixed  $value    The value to test.
+        * @param array  $types    The list of available types.
+        */
+       public function test_get_best_type_for_value( $expected, $value, $types ) {
+               $this->assertEquals( $expected, rest_get_best_type_for_value( $value, $types ) );
+       }
+
+       public function _dp_get_best_type_for_value() {
+               return array(
+                       array(
+                               'array',
+                               array( 'hi' ),
+                               array( 'array' ),
+                       ),
+                       array(
+                               'object',
+                               array( 'hi' => 'there' ),
+                               array( 'object' ),
+                       ),
+                       array(
+                               'integer',
+                               5,
+                               array( 'integer' ),
+                       ),
+                       array(
+                               'number',
+                               4.0,
+                               array( 'number' ),
+                       ),
+                       array(
+                               'boolean',
+                               true,
+                               array( 'boolean' ),
+                       ),
+                       array(
+                               'string',
+                               'str',
+                               array( 'string' ),
+                       ),
+                       array(
+                               'null',
+                               null,
+                               array( 'null' ),
+                       ),
+                       array(
+                               'string',
+                               '',
+                               array( 'array', 'string' ),
+                       ),
+                       array(
+                               'string',
+                               '',
+                               array( 'object', 'string' ),
+                       ),
+                       array(
+                               'string',
+                               'Hello',
+                               array( 'object', 'string' ),
+                       ),
+                       array(
+                               'object',
+                               array( 'hello' => 'world' ),
+                               array( 'object', 'string' ),
+                       ),
+                       array(
+                               'number',
+                               '5.0',
+                               array( 'number', 'string' ),
+                       ),
+                       array(
+                               'string',
+                               '5.0',
+                               array( 'string', 'number' ),
+                       ),
+                       array(
+                               'boolean',
+                               'false',
+                               array( 'boolean', 'string' ),
+                       ),
+                       array(
+                               'string',
+                               'false',
+                               array( 'string', 'boolean' ),
+                       ),
+                       array(
+                               'string',
+                               'a,b',
+                               array( 'string', 'array' ),
+                       ),
+                       array(
+                               'array',
+                               'a,b',
+                               array( 'array', 'string' ),
+                       ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>