Make WordPress Core

Ticket #43392: 43392.5.diff

File 43392.5.diff, 29.6 KB (added by TimothyBlynJacobs, 5 years ago)
  • src/wp-includes/rest-api.php

    diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
    index 716b425e57..e2cdb8ab4f 100644
    a b function rest_validate_value_from_schema( $value, $args, $param = '' ) { 
    11581158                if ( $value instanceof stdClass ) {
    11591159                        $value = (array) $value;
    11601160                }
     1161
     1162                if ( $value instanceof JsonSerializable ) {
     1163                    $value = $value->jsonSerialize();
     1164        }
     1165
    11611166                if ( ! is_array( $value ) ) {
    11621167                        /* translators: 1: parameter, 2: type name */
    11631168                        return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
    function rest_validate_value_from_schema( $value, $args, $param = '' ) { 
    11691174                                if ( is_wp_error( $is_valid ) ) {
    11701175                                        return $is_valid;
    11711176                                }
    1172                         } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
    1173                                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
     1177                        } elseif ( isset( $args['additionalProperties'] ) ) {
     1178                            if ( false === $args['additionalProperties'] ) {
     1179                                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
     1180                            }
     1181
     1182                            if ( is_array( $args['additionalProperties'] ) ) {
     1183                                    $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
     1184                                    if ( is_wp_error( $is_valid ) ) {
     1185                                        return $is_valid;
     1186                    }
     1187                }
    11741188                        }
    11751189                }
    11761190        }
    function rest_sanitize_value_from_schema( $value, $args ) { 
    12971311                if ( $value instanceof stdClass ) {
    12981312                        $value = (array) $value;
    12991313                }
     1314
     1315                if ( $value instanceof JsonSerializable ) {
     1316                    $value = $value->jsonSerialize();
     1317        }
     1318
    13001319                if ( ! is_array( $value ) ) {
    13011320                        return array();
    13021321                }
    function rest_sanitize_value_from_schema( $value, $args ) { 
    13041323                foreach ( $value as $property => $v ) {
    13051324                        if ( isset( $args['properties'][ $property ] ) ) {
    13061325                                $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
    1307                         } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) {
    1308                                 unset( $value[ $property ] );
     1326                        } elseif ( isset( $args['additionalProperties'] ) ) {
     1327                            if ( false === $args['additionalProperties'] ) {
     1328                                    unset( $value[ $property ] );
     1329                            } elseif ( is_array( $args['additionalProperties'] ) ) {
     1330                                $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'] );
     1331                }
    13091332                        }
    13101333                }
    13111334
  • src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    diff --git a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
    index a24a5f649c..0c8fa6e973 100644
    a b abstract class WP_REST_Meta_Fields { 
    143143                         * from the database and then relying on the default value.
    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 ) ) {
    148163                                        return $result;
    abstract class WP_REST_Meta_Fields { 
    150165                                continue;
    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 ) );
    156182                                return $is_valid;
    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'] ) {
    162188                                $result = $this->update_meta_value( $object_id, $meta_key, $name, $value );
    abstract class WP_REST_Meta_Fields { 
    260286                        unset( $to_add[ $add_key ] );
    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, delete_metadata
     290                // will return false for subsequent calls of the same value.
     291                // Use serialization to produce a stable 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 ) {
    267295                        if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
    abstract class WP_REST_Meta_Fields { 
    390418                        $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
    391419                        $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
    392420
    393                         if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
     421                        if ( null === $rest_args['schema']['default'] ) {
     422                                $rest_args['schema']['default'] = $this->get_default_for_type( $type );
     423                        }
     424
     425                        $rest_args['schema'] = $this->default_additional_properties_to_false( $rest_args['schema'] );
     426
     427                        if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ) ) ) {
    394428                                continue;
    395429                        }
    396430
    397431                        if ( empty( $rest_args['single'] ) ) {
    398                                 $rest_args['schema']['items'] = array(
    399                                         'type' => $rest_args['type'],
     432                                $rest_args['schema'] = array(
     433                                        'type'  => 'array',
     434                                        'items' => $rest_args['schema'],
    400435                                );
    401                                 $rest_args['schema']['type']  = 'array';
    402436                        }
    403437
    404438                        $registered[ $name ] = $rest_args;
    abstract class WP_REST_Meta_Fields { 
    449483         * @return mixed Value prepared for output. If a non-JsonSerializable object, null.
    450484         */
    451485        public static function prepare_value( $value, $request, $args ) {
    452                 $type = $args['schema']['type'];
    453486
    454                 // For multi-value fields, check the item type instead.
    455                 if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) {
    456                         $type = $args['schema']['items']['type'];
     487                if ( $args['single'] ) {
     488                        $schema = $args['schema'];
     489                } else {
     490                        $schema = $args['schema']['items'];
    457491                }
    458492
    459                 switch ( $type ) {
    460                         case 'string':
    461                                 $value = (string) $value;
    462                                 break;
    463                         case 'integer':
    464                                 $value = (int) $value;
    465                                 break;
    466                         case 'number':
    467                                 $value = (float) $value;
    468                                 break;
    469                         case 'boolean':
    470                                 $value = (bool) $value;
    471                                 break;
    472                 }
    473 
    474                 // Don't allow objects to be output.
    475                 if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) {
     493                if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
    476494                        return null;
    477495                }
    478496
    479                 return $value;
     497                return rest_sanitize_value_from_schema( $value, $schema );
    480498        }
    481499
    482500        /**
    abstract class WP_REST_Meta_Fields { 
    496514
    497515                return $value;
    498516        }
     517
     518        /**
     519         * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
     520         * is specified.
     521         *
     522         * This is needed to restrict properties of objects in meta values to only
     523         * registered items, as the REST API will allow additional properties by
     524         * default.
     525         *
     526         * @since 5.3.0
     527         *
     528         * @param array $schema The schema array.
     529         * @return array
     530         */
     531        protected function default_additional_properties_to_false( $schema ) {
     532                switch ( $schema['type'] ) {
     533                        case 'object':
     534                                foreach ( $schema['properties'] as $key => $child_schema ) {
     535                                        $schema['properties'][ $key ] = $this->default_additional_properties_to_false( $child_schema );
     536                                }
     537
     538                                if ( ! isset( $schema['additionalProperties'] ) ) {
     539                                        $schema['additionalProperties'] = false;
     540                                }
     541                                break;
     542                        case 'array':
     543                                $schema['items'] = $this->default_additional_properties_to_false( $schema['items'] );
     544                                break;
     545                }
     546
     547                return $schema;
     548        }
     549
     550        /**
     551         * Gets the default value for a schema type.
     552         *
     553         * @since 5.3.0
     554         *
     555         * @param string $type
     556         * @return mixed
     557         */
     558        protected function get_default_for_type( $type ) {
     559                switch ( $type ) {
     560                        case 'string':
     561                                return '';
     562                        case 'boolean':
     563                                return false;
     564                        case 'integer':
     565                                return 0;
     566                        case 'number':
     567                                return 0.0;
     568                        case 'array':
     569                                return [];
     570                        case 'object':
     571                                return [];
     572                        default:
     573                                return null;
     574                }
     575        }
    499576}
  • tests/phpunit/includes/class-basic-object.php

    diff --git a/tests/phpunit/includes/class-basic-object.php b/tests/phpunit/includes/class-basic-object.php
    index 66ac53703e..6165344bde 100644
    a b  
    77 * @since 4.7.0
    88 */
    99
    10 trigger_error( __FILE__ . ' is deprecated since version 5.0.0 with no alternative available.' );
    11 
    1210/**
    1311 * Class used to test accessing methods and properties
    1412 *
  • new file tests/phpunit/includes/class-jsonserializable-object.php

    diff --git a/tests/phpunit/includes/class-jsonserializable-object.php b/tests/phpunit/includes/class-jsonserializable-object.php
    new file mode 100644
    index 0000000000..e58cfb9ebd
    - +  
     1<?php
     2/**
     3 * Unit Tests: JsonSerializbale_Object
     4 *
     5 * @package WordPress
     6 * @subpackage UnitTests
     7 * @since 5.3.0
     8 */
     9
     10class JsonSerializable_Object implements JsonSerializable {
     11
     12        private $data;
     13
     14        public function __construct( $data ) {
     15                $this->data = $data;
     16        }
     17
     18        public function jsonSerialize() {
     19                return $this->data;
     20        }
     21}
  • tests/phpunit/includes/functions.php

    diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php
    index 002e2ea3e6..76f662102c 100644
    a b  
    11<?php
     2require_once __DIR__ . '/class-basic-object.php';
    23
    34/**
    45 * Retrieves PHPUnit runner version.
  • tests/phpunit/tests/rest-api/rest-post-meta-fields.php

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