<!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>[39222] trunk: REST API: Validate and Sanitize registered meta based off the schema.</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 { 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/39222">39222</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/39222","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>joehoyle</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-11-14 16:35:35 +0000 (Mon, 14 Nov 2016)</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: Validate and Sanitize registered meta based off the schema.

With the addition of Array support in our schema validation functions, it's now possible to use these in the meta validation and sanitization steps. Also, this increases the test coverage of using registered via meta the API significantly.

Fixes <a href="https://core.trac.wordpress.org/ticket/38531">#38531</a>.
Props rachelbaker, tharsheblows.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesrestapifieldsclasswprestmetafieldsphp">trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php</a></li>
<li><a href="#trunksrcwpincludesrestapiphp">trunk/src/wp-includes/rest-api.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestpostmetafieldsphp">trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.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>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesrestapifieldsclasswprestmetafieldsphp"></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/fields/class-wp-rest-meta-fields.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php       2016-11-14 11:40:55 UTC (rev 39221)
+++ trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php 2016-11-14 16:35:35 UTC (rev 39222)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -84,7 +84,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $response[ $name ] = $value;
</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">-                return (object) $response;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $response;
</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">@@ -133,10 +133,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">                         */
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( is_null( $request[ $name ] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $result = $this->delete_meta_value( $object_id, $name );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } elseif ( $args['single'] ) {
-                               $result = $this->update_meta_value( $object_id, $name, $request[ $name ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( is_wp_error( $result ) ) {
+                                       return $result;
+                               }
+                               continue;
+                       }
+
+                       $is_valid = rest_validate_value_from_schema( $request[ $name ], $args['schema'], 'meta.' . $name );
+                       if ( is_wp_error( $is_valid ) ) {
+                               $is_valid->add_data( array( 'status' => 400 ) );
+                               return $is_valid;
+                       }
+
+                       $value = rest_sanitize_value_from_schema( $request[ $name ], $args['schema'] );
+
+                       if ( $args['single'] ) {
+                               $result = $this->update_meta_value( $object_id, $name, $value );
</ins><span class="cx" style="display: block; padding: 0 10px">                         } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $result = $this->update_multi_meta_value( $object_id, $name, $value );
</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_wp_error( $result ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -319,12 +333,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $default_args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'name'             => $name,
</span><span class="cx" style="display: block; padding: 0 10px">                                'single'           => $args['single'],
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                'type'             => ! empty( $args['type'] ) ? $args['type'] : null,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'schema'           => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                                'prepare_callback' => array( $this, 'prepare_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">                        $default_schema = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'type'        => null,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'type'        => $default_args['type'],
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'description' => empty( $args['description'] ) ? '' : $args['description'],
</span><span class="cx" style="display: block; padding: 0 10px">                                'default'     => isset( $args['default'] ) ? $args['default'] : null,
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -332,22 +347,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $rest_args = array_merge( $default_args, $rest_args );
</span><span class="cx" style="display: block; padding: 0 10px">                        $rest_args['schema'] = array_merge( $default_schema, $rest_args['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">-                        if ( empty( $rest_args['schema']['type'] ) ) {
-                               // Skip over meta fields that don't have a defined type.
-                               if ( empty( $args['type'] ) ) {
-                                       continue;
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
+                       $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
</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 ( $rest_args['single'] ) {
-                                       $rest_args['schema']['type'] = $args['type'];
-                               } else {
-                                       $rest_args['schema']['type'] = 'array';
-                                       $rest_args['schema']['items'] = array(
-                                               'type' => $args['type'],
-                                       );
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
+                               continue;
</ins><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">+                        if ( empty( $rest_args['single'] ) ) {
+                               $rest_args['schema']['items'] = array(
+                                       'type' => $rest_args['type'],
+                               );
+                               $rest_args['schema']['type'] = 'array';
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $registered[ $rest_args['name'] ] = $rest_args;
</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="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        2016-11-14 11:40:55 UTC (rev 39221)
+++ trunk/src/wp-includes/rest-api.php  2016-11-14 16:35:35 UTC (rev 39222)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -998,6 +998,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_array( $value ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $value = preg_split( '/[\s,]+/', $value );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! wp_is_numeric_array( $value ) ) {
+                       return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
+               }
</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">@@ -1107,6 +1110,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $value as $index => $v ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Normalize to numeric array so nothing unexpected
+               // is in the keys.
+               $value = array_values( $value );
</ins><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">        if ( 'integer' === $args['type'] ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1140,5 +1146,9 @@
</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">+        if ( 'string' === $args['type'] ) {
+               return strval( $value );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         return $value;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestpostmetafieldsphp"></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-post-meta-fields.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php      2016-11-14 11:40:55 UTC (rev 39221)
+++ trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php        2016-11-14 16:35:35 UTC (rev 39222)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,24 +26,29 @@
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_single', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_rest' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'type' => 'string',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ));
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_multi', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_rest' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'type' => 'string',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ));
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_bad_auth', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_rest' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'auth_callback' => '__return_false',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'type' => 'string',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ));
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_bad_auth_multi', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_rest' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => false,
</span><span class="cx" style="display: block; padding: 0 10px">                        'auth_callback' => '__return_false',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'type' => 'string',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ));
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_no_rest', array() );
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_rest_disabled', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'type' => 'string',
</ins><span class="cx" style="display: block; padding: 0 10px">                 ));
</span><span class="cx" style="display: block; padding: 0 10px">                register_meta( 'post', 'test_custom_schema', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => true,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -54,11 +59,25 @@
</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">+                register_meta( 'post', 'test_custom_schema_multi', array(
+                       'single' => false,
+                       'type' => 'integer',
+                       'show_in_rest' => array(
+                               'schema' => array(
+                                       'type' => 'number',
+                               ),
+                       ),
+               ));
</ins><span class="cx" style="display: block; padding: 0 10px">                 register_meta( 'post', 'test_invalid_type', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'single' => true,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'type' => false,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'type' => 'lalala',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'show_in_rest' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                ));
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                register_meta( 'post', 'test_no_type', array(
+                       'single' => true,
+                       'type' => null,
+                       'show_in_rest' => true,
+               ));
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                /** @var WP_REST_Server $wp_rest_server */
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp_rest_server;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -341,6 +360,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $wpdb->show_errors = true;
</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">+        public function test_set_value_invalid_type() {
+               $values = get_post_meta( self::$post_id, 'test_invalid_type', false );
+               $this->assertEmpty( $values );
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'test_invalid_type' => 'test_value',
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertEmpty( get_post_meta( self::$post_id, 'test_invalid_type', false ) );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_set_value_multiple() {
</span><span class="cx" style="display: block; padding: 0 10px">                // Ensure no data exists currently.
</span><span class="cx" style="display: block; padding: 0 10px">                $values = get_post_meta( self::$post_id, 'test_multi', false );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -435,6 +472,92 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEmpty( $meta );
</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">+        public function test_set_value_invalid_value() {
+               register_meta( 'post', 'my_meta_key', array(
+                       'show_in_rest' => true,
+                       'single' => true,
+                       'type' => 'string',
+               ));
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'my_meta_key' => array( 'c', 'n' ),
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+       }
+
+       public function test_set_value_invalid_value_multiple() {
+               register_meta( 'post', 'my_meta_key', array(
+                       'show_in_rest' => true,
+                       'single' => false,
+                       'type' => 'string',
+               ));
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'my_meta_key' => array( array( 'a' ) ),
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+       }
+
+       public function test_set_value_sanitized() {
+               register_meta( 'post', 'my_meta_key', array(
+                       'show_in_rest' => true,
+                       'single' => true,
+                       'type' => 'integer',
+               ));
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'my_meta_key' => '1', // Set to a string.
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $data = $response->get_data();
+               $this->assertEquals( 1, $data['meta']['my_meta_key'] );
+       }
+
+       public function test_set_value_csv() {
+               register_meta( 'post', 'my_meta_key', array(
+                       'show_in_rest' => true,
+                       'single' => false,
+                       'type' => 'integer',
+               ));
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'my_meta_key' => '1,2,3', // Set to a string.
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $data = $response->get_data();
+               $this->assertEquals( array( 1, 2, 3 ), $data['meta']['my_meta_key'] );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @depends test_set_value_multiple
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -485,6 +608,79 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
</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">+        /**
+        * @depends test_get_value
+        */
+       public function test_set_value_single_custom_schema() {
+               // Ensure no data exists currently.
+               $values = get_post_meta( self::$post_id, 'test_custom_schema', false );
+               $this->assertEmpty( $values );
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'test_custom_schema' => 3,
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertEquals( 200, $response->get_status() );
+
+               $meta = get_post_meta( self::$post_id, 'test_custom_schema', false );
+               $this->assertNotEmpty( $meta );
+               $this->assertCount( 1, $meta );
+               $this->assertEquals( 3, $meta[0] );
+
+               $data = $response->get_data();
+               $meta = (array) $data['meta'];
+               $this->assertArrayHasKey( 'test_custom_schema', $meta );
+               $this->assertEquals( 3, $meta['test_custom_schema'] );
+       }
+
+       public function test_set_value_multiple_custom_schema() {
+               // Ensure no data exists currently.
+               $values = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
+               $this->assertEmpty( $values );
+
+               $this->grant_write_permission();
+
+               $data = array(
+                       'meta' => array(
+                               'test_custom_schema_multi' => array( 2 ),
+                       ),
+               );
+               $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertEquals( 200, $response->get_status() );
+
+               $meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
+               $this->assertNotEmpty( $meta );
+               $this->assertCount( 1, $meta );
+               $this->assertEquals( 2, $meta[0] );
+
+               // Add another value.
+               $data = array(
+                       'meta' => array(
+                               'test_custom_schema_multi' => array( 2, 8 ),
+                       ),
+               );
+               $request->set_body_params( $data );
+
+               $response = $this->server->dispatch( $request );
+               $this->assertEquals( 200, $response->get_status() );
+
+               $meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
+               $this->assertNotEmpty( $meta );
+               $this->assertCount( 2, $meta );
+               $this->assertContains( 2, $meta );
+               $this->assertContains( 8, $meta );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_remove_multi_value_db_error() {
</span><span class="cx" style="display: block; padding: 0 10px">                add_post_meta( self::$post_id, 'test_multi', 'val1' );
</span><span class="cx" style="display: block; padding: 0 10px">                $values = get_post_meta( self::$post_id, 'test_multi', false );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -515,6 +711,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
</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">+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_delete_value() {
</span><span class="cx" style="display: block; padding: 0 10px">                add_post_meta( self::$post_id, 'test_single', 'val1' );
</span><span class="cx" style="display: block; padding: 0 10px">                $current = get_post_meta( self::$post_id, 'test_single', true );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -618,6 +815,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayNotHasKey( 'test_no_rest', $meta_schema );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->assertArrayNotHasKey( 'test_no_type', $meta_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></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   2016-11-14 11:40:55 UTC (rev 39221)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php     2016-11-14 16:35:35 UTC (rev 39222)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -76,6 +76,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 1 ), rest_sanitize_value_from_schema( array( '1' ), $schema ) );
</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">+        public function test_type_array_nested() {
+               $schema = array(
+                       'type' => 'array',
+                       'items' => array(
+                               'type' => 'array',
+                               'items' => array(
+                                       'type' => 'number',
+                               ),
+                       ),
+               );
+               $this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
+               $this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( '1' ), array( '2' ) ), $schema ) );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_type_array_as_csv() {
</span><span class="cx" style="display: block; padding: 0 10px">                $schema = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type' => 'array',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -110,4 +124,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 'ribs', 'chicken' ), rest_sanitize_value_from_schema( 'ribs,chicken', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( array( 'chicken', 'coleslaw' ), rest_sanitize_value_from_schema( 'chicken,coleslaw', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       public function test_type_array_is_associative() {
+               $schema = array(
+                       'type' => 'array',
+                       'items' => array(
+                               'type' => 'string',
+                       ),
+               );
+               $this->assertEquals( array( '1', '2' ), rest_sanitize_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
+       }
+
+       public function test_type_unknown() {
+               $schema = array(
+                       'type' => 'lalala',
+               );
+               $this->assertEquals( 'Best lyrics', rest_sanitize_value_from_schema( 'Best lyrics', $schema ) );
+               $this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
+               $this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
+       }
+
+       public function test_no_type() {
+               $schema = array(
+                       'type' => null,
+               );
+               $this->assertEquals( 'Nothing', rest_sanitize_value_from_schema( 'Nothing', $schema ) );
+               $this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
+               $this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $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     2016-11-14 11:40:55 UTC (rev 39221)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php       2016-11-14 16:35:35 UTC (rev 39222)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -104,6 +104,19 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertWPError( rest_validate_value_from_schema( array( true ), $schema ) );
</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">+        public function test_type_array_nested() {
+               $schema = array(
+                       'type' => 'array',
+                       'items' => array(
+                               'type' => 'array',
+                               'items' => array(
+                                       'type' => 'number',
+                               ),
+                       ),
+               );
+               $this->assertTrue( rest_validate_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_type_array_as_csv() {
</span><span class="cx" style="display: block; padding: 0 10px">                $schema = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'type' => 'array',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -139,4 +152,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( 'ribs,chicken', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertWPError( rest_validate_value_from_schema( 'chicken,coleslaw', $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       public function test_type_array_is_associative() {
+               $schema = array(
+                       'type'  => 'array',
+                       'items' => array(
+                               'type' => 'string',
+                       ),
+               );
+               $this->assertWPError( rest_validate_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
+       }
+
+       public function test_type_unknown() {
+               $schema = array(
+                       'type'  => 'lalala',
+               );
+               $this->assertTrue( rest_validate_value_from_schema( 'Best lyrics', $schema ) );
+               $this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
+               $this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>