WordPress.org

Make WordPress Core

Changeset 45807


Ignore:
Timestamp:
08/15/2019 05:16:21 PM (3 months ago)
Author:
kadamwhite
Message:

REST API: Support 'object' and 'array' types in register_meta() schemas.

Extends meta registration to support complex schema values, mirroring the functionality in the settings controller.
Error when trying to modify a meta key containing schema-nonconformant data.

Props @TimothyBlynJacobs, @birgire, @mnelson4, @flixos90.
Fixes #43392.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api.php

    r45771 r45807  
    11601160            $value = (array) $value;
    11611161        }
     1162
     1163        if ( $value instanceof JsonSerializable ) {
     1164            $value = $value->jsonSerialize();
     1165        }
     1166
    11621167        if ( ! is_array( $value ) ) {
    11631168            /* translators: 1: parameter, 2: type name */
     
    11711176                    return $is_valid;
    11721177                }
    1173             } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
    1174                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
     1178            } elseif ( isset( $args['additionalProperties'] ) ) {
     1179                if ( false === $args['additionalProperties'] ) {
     1180                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
     1181                }
     1182
     1183                if ( is_array( $args['additionalProperties'] ) ) {
     1184                    $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
     1185                    if ( is_wp_error( $is_valid ) ) {
     1186                        return $is_valid;
     1187                    }
     1188                }
    11751189            }
    11761190        }
     
    12991313            $value = (array) $value;
    13001314        }
     1315
     1316        if ( $value instanceof JsonSerializable ) {
     1317            $value = $value->jsonSerialize();
     1318        }
     1319
    13011320        if ( ! is_array( $value ) ) {
    13021321            return array();
     
    13061325            if ( isset( $args['properties'][ $property ] ) ) {
    13071326                $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
    1308             } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
    1309                 unset( $value[ $property ] );
     1327            } elseif ( isset( $args['additionalProperties'] ) ) {
     1328                if ( false === $args['additionalProperties'] ) {
     1329                    unset( $value[ $property ] );
     1330                } elseif ( is_array( $args['additionalProperties'] ) ) {
     1331                    $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'] );
     1332                }
    13101333            }
    13111334        }
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    r45681 r45807  
    144144             */
    145145            if ( is_null( $meta[ $name ] ) ) {
     146                $args = $this->get_registered_fields()[ $meta_key ];
     147
     148                if ( $args['single'] ) {
     149                    $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true );
     150
     151                    if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) {
     152                        return new WP_Error(
     153                            'rest_invalid_stored_value',
     154                            /* translators: %s: custom field key */
     155                            sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
     156                            array( 'status' => 500 )
     157                        );
     158                    }
     159                }
     160
    146161                $result = $this->delete_meta_value( $object_id, $meta_key, $name );
    147162                if ( is_wp_error( $result ) ) {
     
    151166            }
    152167
    153             $is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name );
     168            $value = $meta[ $name ];
     169
     170            if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) {
     171                return new WP_Error(
     172                    'rest_invalid_stored_value',
     173                    /* translators: %s: custom field key */
     174                    sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
     175                    array( 'status' => 500 )
     176                );
     177            }
     178
     179            $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name );
    154180            if ( is_wp_error( $is_valid ) ) {
    155181                $is_valid->add_data( array( 'status' => 400 ) );
     
    157183            }
    158184
    159             $value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] );
     185            $value = rest_sanitize_value_from_schema( $value, $args['schema'] );
    160186
    161187            if ( $args['single'] ) {
     
    261287        }
    262288
    263         // `delete_metadata` removes _all_ instances of the value, so only call once.
    264         $to_remove = array_unique( $to_remove );
     289        // `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise,
     290        // `delete_metadata` will return false for subsequent calls of the same value.
     291        // Use serialization to produce a predictable string that can be used by array_unique.
     292        $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) );
    265293
    266294        foreach ( $to_remove as $value ) {
     
    394422            $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
    395423
    396             if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
     424            if ( null === $rest_args['schema']['default'] ) {
     425                $rest_args['schema']['default'] = $this->get_default_for_type( $type );
     426            }
     427
     428            $rest_args['schema'] = $this->default_additional_properties_to_false( $rest_args['schema'] );
     429
     430            if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ) ) ) {
    397431                continue;
    398432            }
    399433
    400434            if ( empty( $rest_args['single'] ) ) {
    401                 $rest_args['schema']['items'] = array(
    402                     'type' => $rest_args['type'],
     435                $rest_args['schema'] = array(
     436                    'type'  => 'array',
     437                    'items' => $rest_args['schema'],
    403438                );
    404                 $rest_args['schema']['type']  = 'array';
    405439            }
    406440
     
    453487     */
    454488    public static function prepare_value( $value, $request, $args ) {
    455         $type = $args['schema']['type'];
    456 
    457         // For multi-value fields, check the item type instead.
    458         if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) {
    459             $type = $args['schema']['items']['type'];
    460         }
    461 
    462         switch ( $type ) {
    463             case 'string':
    464                 $value = (string) $value;
    465                 break;
    466             case 'integer':
    467                 $value = (int) $value;
    468                 break;
    469             case 'number':
    470                 $value = (float) $value;
    471                 break;
    472             case 'boolean':
    473                 $value = (bool) $value;
    474                 break;
    475         }
    476 
    477         // Don't allow objects to be output.
    478         if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) {
     489
     490        if ( $args['single'] ) {
     491            $schema = $args['schema'];
     492        } else {
     493            $schema = $args['schema']['items'];
     494        }
     495
     496        if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
    479497            return null;
    480498        }
    481499
    482         return $value;
     500        return rest_sanitize_value_from_schema( $value, $schema );
    483501    }
    484502
     
    500518        return $value;
    501519    }
     520
     521    /**
     522     * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
     523     * is specified.
     524     *
     525     * This is needed to restrict properties of objects in meta values to only
     526     * registered items, as the REST API will allow additional properties by
     527     * default.
     528     *
     529     * @since 5.3.0
     530     *
     531     * @param array $schema The schema array.
     532     * @return array
     533     */
     534    protected function default_additional_properties_to_false( $schema ) {
     535        switch ( $schema['type'] ) {
     536            case 'object':
     537                foreach ( $schema['properties'] as $key => $child_schema ) {
     538                    $schema['properties'][ $key ] = $this->default_additional_properties_to_false( $child_schema );
     539                }
     540
     541                if ( ! isset( $schema['additionalProperties'] ) ) {
     542                    $schema['additionalProperties'] = false;
     543                }
     544                break;
     545            case 'array':
     546                $schema['items'] = $this->default_additional_properties_to_false( $schema['items'] );
     547                break;
     548        }
     549
     550        return $schema;
     551    }
     552
     553    /**
     554     * Gets the default value for a schema type.
     555     *
     556     * @since 5.3.0
     557     *
     558     * @param string $type
     559     * @return mixed
     560     */
     561    protected function get_default_for_type( $type ) {
     562        switch ( $type ) {
     563            case 'string':
     564                return '';
     565            case 'boolean':
     566                return false;
     567            case 'integer':
     568                return 0;
     569            case 'number':
     570                return 0.0;
     571            case 'array':
     572                return array();
     573            case 'object':
     574                return array();
     575            default:
     576                return null;
     577        }
     578    }
    502579}
  • trunk/tests/phpunit/includes/class-basic-object.php

    r45580 r45807  
    77 * @since 4.7.0
    88 */
    9 
    10 trigger_error( __FILE__ . ' is deprecated since version 5.0.0 with no alternative available.' );
    119
    1210/**
  • trunk/tests/phpunit/includes/functions.php

    r45607 r45807  
    11<?php
     2require_once __DIR__ . '/class-basic-object.php';
    23
    34/**
  • trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    r44113 r45807  
    11771177
    11781178    /**
    1179      * @ticket 38323
     1179     * @ticket       38323
    11801180     * @dataProvider data_get_subtype_meta_value
    11811181     */
     
    12291229
    12301230    /**
    1231      * @ticket 38323
     1231     * @ticket       38323
    12321232     * @dataProvider data_set_subtype_meta_value
    12331233     */
     
    12571257            $this->assertEquals( 403, $response->get_status() );
    12581258            $this->assertEmpty( get_post_meta( $post_id, $meta_key, $single ) );
     1259
    12591260            return;
    12601261        }
     
    12991300
    13001301    /**
    1301      * @ticket 42069
     1302     * @ticket       42069
    13021303     * @dataProvider data_update_value_return_success_with_same_value
    13031304     */
     
    13491350
    13501351    /**
     1352     * @ticket 43392
     1353     */
     1354    public function test_object_single() {
     1355        $this->grant_write_permission();
     1356
     1357        register_post_meta(
     1358            'post',
     1359            'object',
     1360            array(
     1361                'single'       => true,
     1362                'type'         => 'object',
     1363                'show_in_rest' => array(
     1364                    'schema' => array(
     1365                        'type'       => 'object',
     1366                        'properties' => array(
     1367                            'project' => array(
     1368                                'type' => 'string',
     1369                            ),
     1370                        ),
     1371                    ),
     1372                ),
     1373            )
     1374        );
     1375
     1376        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1377        $request->set_body_params(
     1378            array(
     1379                'meta' => array(
     1380                    'object' => array(
     1381                        'project' => 'WordPress',
     1382                    ),
     1383                ),
     1384            )
     1385        );
     1386
     1387        $response = rest_get_server()->dispatch( $request );
     1388        $data     = $response->get_data();
     1389
     1390        $this->assertArrayHasKey( 'object', $data['meta'] );
     1391        $this->assertArrayHasKey( 'project', $data['meta']['object'] );
     1392        $this->assertEquals( 'WordPress', $data['meta']['object']['project'] );
     1393
     1394        $meta = get_post_meta( self::$post_id, 'object', true );
     1395        $this->assertArrayHasKey( 'project', $meta );
     1396        $this->assertEquals( 'WordPress', $meta['project'] );
     1397    }
     1398
     1399    /**
     1400     * @ticket 43392
     1401     */
     1402    public function test_object_multiple() {
     1403        $this->grant_write_permission();
     1404
     1405        register_post_meta(
     1406            'post',
     1407            'object',
     1408            array(
     1409                'single'       => false,
     1410                'type'         => 'object',
     1411                'show_in_rest' => array(
     1412                    'schema' => array(
     1413                        'type'       => 'object',
     1414                        'properties' => array(
     1415                            'project' => array(
     1416                                'type' => 'string',
     1417                            ),
     1418                        ),
     1419                    ),
     1420                ),
     1421            )
     1422        );
     1423
     1424        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1425        $request->set_body_params(
     1426            array(
     1427                'meta' => array(
     1428                    'object' => array(
     1429                        array(
     1430                            'project' => 'WordPress',
     1431                        ),
     1432                        array(
     1433                            'project' => 'bbPress',
     1434                        ),
     1435                    ),
     1436                ),
     1437            )
     1438        );
     1439
     1440        $response = rest_get_server()->dispatch( $request );
     1441        $data     = $response->get_data();
     1442
     1443        $this->assertArrayHasKey( 'object', $data['meta'] );
     1444        $this->assertCount( 2, $data['meta']['object'] );
     1445
     1446        $this->assertArrayHasKey( 'project', $data['meta']['object'][0] );
     1447        $this->assertEquals( 'WordPress', $data['meta']['object'][0]['project'] );
     1448
     1449        $this->assertArrayHasKey( 'project', $data['meta']['object'][1] );
     1450        $this->assertEquals( 'bbPress', $data['meta']['object'][1]['project'] );
     1451
     1452        $meta = get_post_meta( self::$post_id, 'object' );
     1453
     1454        $this->assertCount( 2, $meta );
     1455
     1456        $this->assertArrayHasKey( 'project', $meta[0] );
     1457        $this->assertEquals( 'WordPress', $meta[0]['project'] );
     1458
     1459        $this->assertArrayHasKey( 'project', $meta[1] );
     1460        $this->assertEquals( 'bbPress', $meta[1]['project'] );
     1461    }
     1462
     1463    /**
     1464     * @ticket 43392
     1465     */
     1466    public function test_array_single() {
     1467        $this->grant_write_permission();
     1468
     1469        register_post_meta(
     1470            'post',
     1471            'list',
     1472            array(
     1473                'single'       => true,
     1474                'type'         => 'array',
     1475                'show_in_rest' => array(
     1476                    'schema' => array(
     1477                        'type'  => 'array',
     1478                        'items' => array(
     1479                            'type' => 'string',
     1480                        ),
     1481                    ),
     1482                ),
     1483            )
     1484        );
     1485
     1486        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1487        $request->set_body_params(
     1488            array(
     1489                'meta' => array(
     1490                    'list' => array( 'WordPress', 'bbPress' ),
     1491                ),
     1492            )
     1493        );
     1494
     1495        $response = rest_get_server()->dispatch( $request );
     1496        $data     = $response->get_data();
     1497
     1498        $this->assertArrayHasKey( 'list', $data['meta'] );
     1499        $this->assertEquals( array( 'WordPress', 'bbPress' ), $data['meta']['list'] );
     1500
     1501        $meta = get_post_meta( self::$post_id, 'list', true );
     1502        $this->assertEquals( array( 'WordPress', 'bbPress' ), $meta );
     1503    }
     1504
     1505    /**
     1506     * @ticket 43392
     1507     */
     1508    public function test_array_of_objects_multiple() {
     1509        $this->grant_write_permission();
     1510
     1511        register_post_meta(
     1512            'post',
     1513            'list_of_objects',
     1514            array(
     1515                'single'       => false,
     1516                'type'         => 'array',
     1517                'show_in_rest' => array(
     1518                    'schema' => array(
     1519                        'type'  => 'array',
     1520                        'items' => array(
     1521                            'type'       => 'object',
     1522                            'properties' => array(
     1523                                'version' => array(
     1524                                    'type' => 'string',
     1525                                ),
     1526                                'artist'  => array(
     1527                                    'type' => 'string',
     1528                                ),
     1529                            ),
     1530                        ),
     1531                    ),
     1532                ),
     1533            )
     1534        );
     1535
     1536        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1537        $request->set_body_params(
     1538            array(
     1539                'meta' => array(
     1540                    'list_of_objects' => array(
     1541                        // Meta 1
     1542                        array(
     1543                            array(
     1544                                'version' => '5.2',
     1545                                'artist'  => 'Jaco',
     1546                            ),
     1547                            array(
     1548                                'version' => '5.1',
     1549                                'artist'  => 'Betty',
     1550                            ),
     1551                        ),
     1552                        // Meta 2
     1553                        array(
     1554                            array(
     1555                                'version' => '4.9',
     1556                                'artist'  => 'Tipton',
     1557                            ),
     1558                        ),
     1559                    ),
     1560                ),
     1561            )
     1562        );
     1563
     1564        $response = rest_get_server()->dispatch( $request );
     1565        $data     = $response->get_data();
     1566
     1567        $this->assertArrayHasKey( 'list_of_objects', $data['meta'] );
     1568        $this->assertCount( 2, $data['meta']['list_of_objects'] );
     1569
     1570        $this->assertEquals(
     1571            array(
     1572                array(
     1573                    'version' => '5.2',
     1574                    'artist'  => 'Jaco',
     1575                ),
     1576                array(
     1577                    'version' => '5.1',
     1578                    'artist'  => 'Betty',
     1579                ),
     1580            ),
     1581            $data['meta']['list_of_objects'][0]
     1582        );
     1583
     1584        $this->assertEquals(
     1585            array(
     1586                array(
     1587                    'version' => '4.9',
     1588                    'artist'  => 'Tipton',
     1589                ),
     1590            ),
     1591            $data['meta']['list_of_objects'][1]
     1592        );
     1593
     1594        $meta = get_post_meta( self::$post_id, 'list_of_objects' );
     1595
     1596        $this->assertCount( 2, $meta );
     1597
     1598        $this->assertEquals(
     1599            array(
     1600                array(
     1601                    'version' => '5.2',
     1602                    'artist'  => 'Jaco',
     1603                ),
     1604                array(
     1605                    'version' => '5.1',
     1606                    'artist'  => 'Betty',
     1607                ),
     1608            ),
     1609            $meta[0]
     1610        );
     1611
     1612        $this->assertEquals(
     1613            array(
     1614                array(
     1615                    'version' => '4.9',
     1616                    'artist'  => 'Tipton',
     1617                ),
     1618            ),
     1619            $meta[1]
     1620        );
     1621    }
     1622
     1623    /**
     1624     * @ticket 43392
     1625     */
     1626    public function test_php_objects_returned_as_null() {
     1627        register_post_meta(
     1628            'post',
     1629            'object',
     1630            array(
     1631                'single'       => true,
     1632                'type'         => 'object',
     1633                'show_in_rest' => array(
     1634                    'schema' => array(
     1635                        'type'       => 'object',
     1636                        'properties' => array(
     1637                            'project' => array(
     1638                                'type' => 'string',
     1639                            ),
     1640                        ),
     1641                    ),
     1642                ),
     1643            )
     1644        );
     1645
     1646        $basic          = new Basic_Object();
     1647        $basic->project = 'WordPress';
     1648        update_post_meta( self::$post_id, 'object', $basic );
     1649
     1650        $request  = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1651        $response = rest_get_server()->dispatch( $request );
     1652        $data     = $response->get_data();
     1653
     1654        $this->assertArrayHasKey( 'object', $data['meta'] );
     1655        $this->assertNull( $data['meta']['object'] );
     1656    }
     1657
     1658    /**
     1659     * @ticket 43392
     1660     */
     1661    public function test_php_objects_returned_as_null_multiple() {
     1662        register_post_meta(
     1663            'post',
     1664            'object',
     1665            array(
     1666                'single'       => false,
     1667                'type'         => 'object',
     1668                'show_in_rest' => array(
     1669                    'schema' => array(
     1670                        'type'       => 'object',
     1671                        'properties' => array(
     1672                            'project' => array(
     1673                                'type' => 'string',
     1674                            ),
     1675                        ),
     1676                    ),
     1677                ),
     1678            )
     1679        );
     1680
     1681        $basic          = new Basic_Object();
     1682        $basic->project = 'WordPress';
     1683        add_post_meta( self::$post_id, 'object', array( 'project' => 'bbPress' ) );
     1684        add_post_meta( self::$post_id, 'object', $basic );
     1685
     1686        $request  = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1687        $response = rest_get_server()->dispatch( $request );
     1688        $data     = $response->get_data();
     1689
     1690        $this->assertArrayHasKey( 'object', $data['meta'] );
     1691        $this->assertCount( 2, $data['meta']['object'] );
     1692        $this->assertEquals( array( 'project' => 'bbPress' ), $data['meta']['object'][0] );
     1693        $this->assertNull( $data['meta']['object'][1] );
     1694    }
     1695
     1696    /**
     1697     * @ticket 43392
     1698     */
     1699    public function test_php_jsonserializable_object_returns_value() {
     1700        require_once __DIR__ . '/../../includes/class-jsonserializable-object.php';
     1701
     1702        register_post_meta(
     1703            'post',
     1704            'object',
     1705            array(
     1706                'single'       => true,
     1707                'type'         => 'object',
     1708                'show_in_rest' => array(
     1709                    'schema' => array(
     1710                        'type'       => 'object',
     1711                        'properties' => array(
     1712                            'project' => array(
     1713                                'type' => 'string',
     1714                            ),
     1715                        ),
     1716                    ),
     1717                ),
     1718            )
     1719        );
     1720
     1721        update_post_meta( self::$post_id, 'object', new JsonSerializable_Object( array( 'project' => 'WordPress' ) ) );
     1722
     1723        $request  = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1724        $response = rest_get_server()->dispatch( $request );
     1725        $data     = $response->get_data();
     1726
     1727        $this->assertArrayHasKey( 'object', $data['meta'] );
     1728        $this->assertEquals( array( 'project' => 'WordPress' ), $data['meta']['object'] );
     1729    }
     1730
     1731    /**
     1732     * @ticket 43392
     1733     */
     1734    public function test_updating_meta_to_null_for_key_with_existing_php_object_does_not_delete_meta_value() {
     1735        $this->grant_write_permission();
     1736
     1737        register_post_meta(
     1738            'post',
     1739            'object',
     1740            array(
     1741                'single'       => true,
     1742                'type'         => 'object',
     1743                'show_in_rest' => array(
     1744                    'schema' => array(
     1745                        'type'       => 'object',
     1746                        'properties' => array(
     1747                            'project' => array(
     1748                                'type' => 'string',
     1749                            ),
     1750                        ),
     1751                    ),
     1752                ),
     1753            )
     1754        );
     1755
     1756        $basic          = new Basic_Object();
     1757        $basic->project = 'WordPress';
     1758        update_post_meta( self::$post_id, 'object', $basic );
     1759
     1760        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1761        $request->set_body_params(
     1762            array(
     1763                'meta' => array(
     1764                    'object' => null,
     1765                ),
     1766            )
     1767        );
     1768
     1769        $response = rest_get_server()->dispatch( $request );
     1770        $this->assertEquals( 500, $response->get_status() );
     1771    }
     1772
     1773    /**
     1774     * @ticket 43392
     1775     */
     1776    public function test_updating_non_single_meta_to_null_for_key_with_existing_php_object_does_not_set_meta_value_to_null() {
     1777        $this->grant_write_permission();
     1778
     1779        register_post_meta(
     1780            'post',
     1781            'object',
     1782            array(
     1783                'single'       => false,
     1784                'type'         => 'object',
     1785                'show_in_rest' => array(
     1786                    'schema' => array(
     1787                        'type'       => 'object',
     1788                        'properties' => array(
     1789                            'project' => array(
     1790                                'type' => 'string',
     1791                            ),
     1792                        ),
     1793                    ),
     1794                ),
     1795            )
     1796        );
     1797
     1798        $basic          = new Basic_Object();
     1799        $basic->project = 'WordPress';
     1800        add_post_meta( self::$post_id, 'object', array( 'project' => 'bbPress' ) );
     1801        add_post_meta( self::$post_id, 'object', $basic );
     1802
     1803        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1804        $request->set_body_params(
     1805            array(
     1806                'meta' => array(
     1807                    'object' => array(
     1808                        array( 'project' => 'BuddyPress' ),
     1809                        null,
     1810                    ),
     1811                ),
     1812            )
     1813        );
     1814
     1815        $response = rest_get_server()->dispatch( $request );
     1816        $this->assertEquals( 500, $response->get_status() );
     1817    }
     1818
     1819    /**
     1820     * @ticket 43392
     1821     */
     1822    public function test_object_rejects_additional_properties_by_default() {
     1823        $this->grant_write_permission();
     1824
     1825        register_post_meta(
     1826            'post',
     1827            'object',
     1828            array(
     1829                'single'       => true,
     1830                'type'         => 'object',
     1831                'show_in_rest' => array(
     1832                    'schema' => array(
     1833                        'type'       => 'object',
     1834                        'properties' => array(
     1835                            'project' => array(
     1836                                'type' => 'string',
     1837                            ),
     1838                        ),
     1839                    ),
     1840                ),
     1841            )
     1842        );
     1843
     1844        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1845        $request->set_body_params(
     1846            array(
     1847                'meta' => array(
     1848                    'object' => array(
     1849                        'project'     => 'BuddyPress',
     1850                        'awesomeness' => 100,
     1851                    ),
     1852                ),
     1853            )
     1854        );
     1855
     1856        $response = rest_get_server()->dispatch( $request );
     1857        $this->assertEquals( 400, $response->get_status() );
     1858    }
     1859
     1860    /**
     1861     * @ticket 43392
     1862     */
     1863    public function test_object_allows_additional_properties_if_explicitly_set() {
     1864        $this->grant_write_permission();
     1865
     1866        $value = array(
     1867            'project'     => 'BuddyPress',
     1868            'awesomeness' => 100,
     1869        );
     1870
     1871        register_post_meta(
     1872            'post',
     1873            'object',
     1874            array(
     1875                'single'       => true,
     1876                'type'         => 'object',
     1877                'show_in_rest' => array(
     1878                    'schema' => array(
     1879                        'type'                 => 'object',
     1880                        'additionalProperties' => true,
     1881                        'properties'           => array(
     1882                            'project' => array(
     1883                                'type' => 'string',
     1884                            ),
     1885                        ),
     1886                    ),
     1887                ),
     1888            )
     1889        );
     1890
     1891        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1892        $request->set_body_params(
     1893            array(
     1894                'meta' => array(
     1895                    'object' => $value,
     1896                ),
     1897            )
     1898        );
     1899
     1900        $response = rest_get_server()->dispatch( $request );
     1901        $this->assertEquals( 200, $response->get_status() );
     1902        $this->assertEquals( $value, $response->get_data()['meta']['object'] );
     1903
     1904        $this->assertEquals( $value, get_post_meta( self::$post_id, 'object', true ) );
     1905    }
     1906
     1907    /**
     1908     * @ticket 43392
     1909     */
     1910    public function test_object_allows_additional_properties_and_uses_its_schema() {
     1911        $this->grant_write_permission();
     1912
     1913        $value = array(
     1914            'project'     => 'BuddyPress',
     1915            'awesomeness' => 'fabulous',
     1916        );
     1917
     1918        register_post_meta(
     1919            'post',
     1920            'object',
     1921            array(
     1922                'single'       => true,
     1923                'type'         => 'object',
     1924                'show_in_rest' => array(
     1925                    'schema' => array(
     1926                        'type'                 => 'object',
     1927                        'additionalProperties' => array(
     1928                            'type' => 'number',
     1929                        ),
     1930                        'properties'           => array(
     1931                            'project' => array(
     1932                                'type' => 'string',
     1933                            ),
     1934                        ),
     1935                    ),
     1936                ),
     1937            )
     1938        );
     1939
     1940        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1941        $request->set_body_params(
     1942            array(
     1943                'meta' => array(
     1944                    'object' => $value,
     1945                ),
     1946            )
     1947        );
     1948
     1949        $response = rest_get_server()->dispatch( $request );
     1950        $this->assertEquals( 400, $response->get_status() );
     1951    }
     1952
     1953    /**
     1954     * @ticket 43392
     1955     */
     1956    public function test_invalid_meta_value_are_set_to_null_in_response() {
     1957        register_post_meta(
     1958            'post',
     1959            'email',
     1960            array(
     1961                'single'       => true,
     1962                'type'         => 'string',
     1963                'show_in_rest' => array(
     1964                    'schema' => array(
     1965                        'type'   => 'string',
     1966                        'format' => 'email',
     1967                    ),
     1968                ),
     1969            )
     1970        );
     1971
     1972        update_post_meta( self::$post_id, 'email', 'invalid_meta_value' );
     1973
     1974        $request  = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1975        $response = rest_get_server()->dispatch( $request );
     1976
     1977        $this->assertNull( $response->get_data()['meta']['email'] );
     1978    }
     1979
     1980    /**
     1981     * @ticket 43392
     1982     */
     1983    public function test_meta_values_are_not_set_to_null_in_response_if_type_safely_serializable() {
     1984        register_post_meta(
     1985            'post',
     1986            'boolean',
     1987            array(
     1988                'single'       => true,
     1989                'show_in_rest' => true,
     1990                'type'         => 'boolean',
     1991            )
     1992        );
     1993
     1994        update_post_meta( self::$post_id, 'boolean', 'true' );
     1995
     1996        $request  = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1997        $response = rest_get_server()->dispatch( $request );
     1998
     1999        $this->assertTrue( $response->get_data()['meta']['boolean'] );
     2000    }
     2001
     2002    /**
     2003     * @ticket 43392
     2004     */
     2005    public function test_update_multi_meta_value_object() {
     2006        register_post_meta(
     2007            'post',
     2008            'object',
     2009            array(
     2010                'single'       => false,
     2011                'type'         => 'object',
     2012                'show_in_rest' => array(
     2013                    'schema' => array(
     2014                        'type'       => 'object',
     2015                        'properties' => array(
     2016                            'project' => array(
     2017                                'type' => 'string',
     2018                            ),
     2019                        ),
     2020                    ),
     2021                ),
     2022            )
     2023        );
     2024
     2025        add_post_meta(
     2026            self::$post_id,
     2027            'object',
     2028            array(
     2029                'project' => 'WordPress',
     2030            )
     2031        );
     2032        add_post_meta(
     2033            self::$post_id,
     2034            'object',
     2035            array(
     2036                'project' => 'bbPress',
     2037            )
     2038        );
     2039
     2040        $this->grant_write_permission();
     2041
     2042        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2043        $request->set_body_params(
     2044            array(
     2045                'meta' => array(
     2046                    'object' => array(
     2047                        array( 'project' => 'WordPress' ),
     2048                        array( 'project' => 'BuddyPress' ),
     2049                    ),
     2050                ),
     2051            )
     2052        );
     2053
     2054        $response = rest_get_server()->dispatch( $request );
     2055        $this->assertEquals( 200, $response->get_status() );
     2056
     2057        $data = $response->get_data();
     2058        $this->assertArrayHasKey( 'object', $data['meta'] );
     2059
     2060        $this->assertCount( 2, $data['meta']['object'] );
     2061        $this->assertEquals( array( 'project' => 'WordPress' ), $data['meta']['object'][0] );
     2062        $this->assertEquals( array( 'project' => 'BuddyPress' ), $data['meta']['object'][1] );
     2063
     2064        $meta = get_post_meta( self::$post_id, 'object' );
     2065        $this->assertCount( 2, $meta );
     2066        $this->assertEquals( array( 'project' => 'WordPress' ), $meta[0] );
     2067        $this->assertEquals( array( 'project' => 'BuddyPress' ), $meta[1] );
     2068    }
     2069
     2070    /**
     2071     * @ticket 43392
     2072     */
     2073    public function test_update_multi_meta_value_array() {
     2074        register_post_meta(
     2075            'post',
     2076            'list',
     2077            array(
     2078                'type'         => 'array',
     2079                'show_in_rest' => array(
     2080                    'schema' => array(
     2081                        'type'  => 'array',
     2082                        'items' => array(
     2083                            'type' => 'string',
     2084                        ),
     2085                    ),
     2086                ),
     2087            )
     2088        );
     2089
     2090        add_post_meta( self::$post_id, 'list', array( 'WordPress', 'bbPress' ) );
     2091        add_post_meta( self::$post_id, 'list', array( 'WordCamp' ) );
     2092
     2093        $this->grant_write_permission();
     2094
     2095        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2096        $request->set_body_params(
     2097            array(
     2098                'meta' => array(
     2099                    'list' => array(
     2100                        array( 'WordPress', 'bbPress' ),
     2101                        array( 'BuddyPress' ),
     2102                    ),
     2103                ),
     2104            )
     2105        );
     2106
     2107        $response = rest_get_server()->dispatch( $request );
     2108        $this->assertEquals( 200, $response->get_status() );
     2109
     2110        $data = $response->get_data();
     2111        $this->assertArrayHasKey( 'list', $data['meta'] );
     2112
     2113        $this->assertCount( 2, $data['meta']['list'] );
     2114        $this->assertEquals( array( 'WordPress', 'bbPress' ), $data['meta']['list'][0] );
     2115        $this->assertEquals( array( 'BuddyPress' ), $data['meta']['list'][1] );
     2116
     2117        $meta = get_post_meta( self::$post_id, 'list' );
     2118        $this->assertCount( 2, $meta );
     2119        $this->assertEquals( array( 'WordPress', 'bbPress' ), $meta[0] );
     2120        $this->assertEquals( array( 'BuddyPress' ), $meta[1] );
     2121    }
     2122
     2123    /**
    13512124     * Internal function used to disable an insert query which
    13522125     * will trigger a wpdb error for testing purposes.
Note: See TracChangeset for help on using the changeset viewer.