WordPress.org

Make WordPress Core

Ticket #44975: 44975.6.diff

File 44975.6.diff, 23.0 KB (added by TimothyBlynJacobs, 13 months ago)
  • src/wp-includes/rest-api.php

    diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
    index 4b49ebfdcb..5bd3419fe8 100644
    a b function rest_get_avatar_sizes() { 
    11441144 * @return true|WP_Error
    11451145 */
    11461146function rest_validate_value_from_schema( $value, $args, $param = '' ) {
     1147        if ( is_array( $args['type'] ) ) {
     1148                foreach ( $args['type'] as $type ) {
     1149                        $type_args         = $args;
     1150                        $type_args['type'] = $type;
     1151
     1152                        if ( true === rest_validate_value_from_schema( $value, $type_args, $param ) ) {
     1153                                return true;
     1154                        }
     1155                }
     1156
     1157                /* translators: 1: Parameter, 2: List of types. */
     1158                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s' ), $param, implode( ',', $args['type'] ) ) );
     1159        }
     1160
    11471161        if ( 'array' === $args['type'] ) {
    11481162                if ( ! is_null( $value ) ) {
    11491163                        $value = wp_parse_list( $value );
    function rest_validate_value_from_schema( $value, $args, $param = '' ) { 
    11961210                }
    11971211        }
    11981212
     1213        if ( 'null' === $args['type'] ) {
     1214                if ( null !== $value ) {
     1215                        /* translators: 1: Parameter, 2: Type name. */
     1216                        return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
     1217                }
     1218
     1219                return true;
     1220        }
     1221
    11991222        if ( ! empty( $args['enum'] ) ) {
    12001223                if ( ! in_array( $value, $args['enum'], true ) ) {
    12011224                        /* translators: 1: Parameter, 2: List of valid values. */
    function rest_validate_value_from_schema( $value, $args, $param = '' ) { 
    13001323 * @return true|WP_Error
    13011324 */
    13021325function rest_sanitize_value_from_schema( $value, $args ) {
     1326        if ( is_array( $args['type'] ) ) {
     1327                // Determine which type the value was validated against, and use that type when performing sanitization
     1328                $validated_type = '';
     1329
     1330                foreach ( $args['type'] as $type ) {
     1331                        $type_args         = $args;
     1332                        $type_args['type'] = $type;
     1333
     1334                        if ( ! is_wp_error( rest_validate_value_from_schema( $value, $type_args ) ) ) {
     1335                                $validated_type = $type;
     1336                                break;
     1337                        }
     1338                }
     1339
     1340                if ( ! $validated_type ) {
     1341                        return null;
     1342                }
     1343
     1344                $args['type'] = $validated_type;
     1345        }
     1346
    13031347        if ( 'array' === $args['type'] ) {
    13041348                if ( empty( $args['items'] ) ) {
    13051349                        return (array) $value;
    function rest_sanitize_value_from_schema( $value, $args ) { 
    13421386                return $value;
    13431387        }
    13441388
     1389        if ( 'null' === $args['type'] ) {
     1390                return null;
     1391        }
     1392
    13451393        if ( 'integer' === $args['type'] ) {
    13461394                return (int) $value;
    13471395        }
  • src/wp-includes/rest-api/class-wp-rest-request.php

    diff --git a/src/wp-includes/rest-api/class-wp-rest-request.php b/src/wp-includes/rest-api/class-wp-rest-request.php
    index 7b2a32e618..3c67b6ad2a 100644
    a b class WP_REST_Request implements ArrayAccess { 
    397397                return null;
    398398        }
    399399
     400        /**
     401         * Checks if a parameter exists in the request.
     402         *
     403         * This allows distinguishing between an omitted parameter,
     404         * and a parameter specifically set to null.
     405         *
     406         * @since 5.3.0
     407         *
     408         * @param string $key Parameter name.
     409         *
     410         * @return bool True if a param exists for the given key.
     411         */
     412        public function has_param( $key ) {
     413                $order = $this->get_parameter_order();
     414
     415                foreach ( $order as $type ) {
     416                        if ( array_key_exists( $key, $this->params[ $type ] ) ) {
     417                                return true;
     418                        }
     419                }
     420
     421                return false;
     422        }
     423
    400424        /**
    401425         * Sets a parameter on the request.
    402426         *
  • src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
    index e53ff4b910..c0304de164 100644
    a b class WP_REST_Posts_Controller extends WP_REST_Controller { 
    10361036                        }
    10371037                }
    10381038
     1039                // Sending a null date or date_gmt value resets date and date_gmt to their
     1040                // default values (`0000-00-00 00:00:00`).
     1041                if (
     1042                        ( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) ||
     1043                        ( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] )
     1044                ) {
     1045                        $prepared_post->post_date_gmt = null;
     1046                        $prepared_post->post_date     = null;
     1047                }
     1048
    10391049                // Post slug.
    10401050                if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
    10411051                        $prepared_post->post_name = $request['slug'];
    class WP_REST_Posts_Controller extends WP_REST_Controller { 
    18811891                        'properties' => array(
    18821892                                'date'         => array(
    18831893                                        'description' => __( "The date the object was published, in the site's timezone." ),
    1884                                         'type'        => 'string',
     1894                                        'type'        => array( 'string', 'null' ),
    18851895                                        'format'      => 'date-time',
    18861896                                        'context'     => array( 'view', 'edit', 'embed' ),
    18871897                                ),
    18881898                                'date_gmt'     => array(
    18891899                                        'description' => __( 'The date the object was published, as GMT.' ),
    1890                                         'type'        => 'string',
     1900                                        'type'        => array( 'string', 'null' ),
    18911901                                        'format'      => 'date-time',
    18921902                                        'context'     => array( 'view', 'edit' ),
    18931903                                ),
  • tests/phpunit/tests/rest-api/rest-posts-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php
    index ba174c448e..9d0a97f324 100644
    a b class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te 
    25552555                $this->assertEquals( $params['excerpt'], $post->post_excerpt );
    25562556        }
    25572557
     2558        /**
     2559         * Verify that updating a post with a `null` date or date_gmt results in a reset post, where all
     2560         * date values are equal (date, date_gmt, date_modified and date_modofied_gmt) in the API response.
     2561         * In the database, the post_date_gmt field is reset to the default `0000-00-00 00:00:00`.
     2562         *
     2563         * @ticket 44975
     2564         */
     2565        public function test_rest_update_post_with_empty_date() {
     2566                // Create a new test post.
     2567                $post_id = $this->factory->post->create();
     2568                wp_set_current_user( self::$editor_id );
     2569
     2570                // Set the post date to the future.
     2571                $future_date = '2919-07-29T18:00:00';
     2572                $request     = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
     2573                $request->add_header( 'content-type', 'application/json' );
     2574                $params = $this->set_post_data(
     2575                        array(
     2576                                'date_gmt' => $future_date,
     2577                                'date'     => $future_date,
     2578                                'title'    => 'update',
     2579                                'status'   => 'draft',
     2580                        )
     2581                );
     2582                $request->set_body( wp_json_encode( $params ) );
     2583                $response = rest_get_server()->dispatch( $request );
     2584                $this->check_update_post_response( $response );
     2585                $new_data = $response->get_data();
     2586
     2587                // Verify the post is set to the future date.
     2588                $this->assertEquals( $new_data['date_gmt'], $future_date );
     2589                $this->assertEquals( $new_data['date'], $future_date );
     2590                $this->assertNotEquals( $new_data['date_gmt'], $new_data['modified_gmt'] );
     2591                $this->assertNotEquals( $new_data['date'], $new_data['modified'] );
     2592
     2593                // Update post with a blank field (date or date_gmt).
     2594                $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
     2595                $request->add_header( 'content-type', 'application/json' );
     2596                $params = $this->set_post_data(
     2597                        array(
     2598                                'date_gmt' => null,
     2599                                'title'    => 'test',
     2600                                'status'   => 'draft',
     2601                        )
     2602                );
     2603                $request->set_body( wp_json_encode( $params ) );
     2604                $response = rest_get_server()->dispatch( $request );
     2605
     2606                // Verify the date field values are reset in the API response.
     2607                $this->check_update_post_response( $response );
     2608                $new_data = $response->get_data();
     2609                $this->assertEquals( $new_data['date_gmt'], $new_data['date'] );
     2610                $this->assertNotEquals( $new_data['date_gmt'], $future_date );
     2611
     2612                $post = get_post( $post_id, 'ARRAY_A' );
     2613                $this->assertEquals( $post['post_date_gmt'], '0000-00-00 00:00:00' );
     2614                $this->assertNotEquals( $new_data['date_gmt'], $future_date );
     2615                $this->assertNotEquals( $new_data['date'], $future_date );
     2616        }
     2617
    25582618        public function test_rest_update_post_raw() {
    25592619                wp_set_current_user( self::$editor_id );
    25602620
  • tests/phpunit/tests/rest-api/rest-schema-sanitization.php

    diff --git a/tests/phpunit/tests/rest-api/rest-schema-sanitization.php b/tests/phpunit/tests/rest-api/rest-schema-sanitization.php
    index 4f9f2c9242..1b2fd1d90c 100644
    a b class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase { 
    292292                $this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
    293293                $this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
    294294        }
     295
     296        public function test_nullable_date() {
     297                $schema = array(
     298                        'type'   => array( 'string', 'null' ),
     299                        'format' => 'date-time',
     300                );
     301
     302                $this->assertNull( rest_sanitize_value_from_schema( null, $schema ) );
     303                $this->assertEquals( '2019-09-19T18:00:00', rest_sanitize_value_from_schema( '2019-09-19T18:00:00', $schema ) );
     304                $this->assertNull( rest_sanitize_value_from_schema( 'lalala', $schema ) );
     305        }
     306
     307        public function test_object_or_string() {
     308                $schema = array(
     309                        'type'       => array( 'object', 'string' ),
     310                        'properties' => array(
     311                                'raw' => array(
     312                                        'type' => 'string',
     313                                ),
     314                        ),
     315                );
     316
     317                $this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
     318                $this->assertEquals( array( 'raw' => 'My Value' ), rest_sanitize_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
     319                $this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
     320        }
     321
     322        public function test_object_or_bool() {
     323                $schema = array(
     324                        'type'       => array( 'object', 'boolean' ),
     325                        'properties' => array(
     326                                'raw' => array(
     327                                        'type' => 'boolean',
     328                                ),
     329                        ),
     330                );
     331
     332                $this->assertTrue( rest_sanitize_value_from_schema( true, $schema ) );
     333                $this->assertTrue( rest_sanitize_value_from_schema( '1', $schema ) );
     334                $this->assertTrue( rest_sanitize_value_from_schema( 1, $schema ) );
     335
     336                $this->assertFalse( rest_sanitize_value_from_schema( false, $schema ) );
     337                $this->assertFalse( rest_sanitize_value_from_schema( '0', $schema ) );
     338                $this->assertFalse( rest_sanitize_value_from_schema( 0, $schema ) );
     339
     340                $this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => true ), $schema ) );
     341                $this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => '1' ), $schema ) );
     342                $this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
     343
     344                $this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => false ), $schema ) );
     345                $this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => '0' ), $schema ) );
     346                $this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => 0 ), $schema ) );
     347
     348                $this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
     349        }
    295350}
  • tests/phpunit/tests/rest-api/rest-schema-validation.php

    diff --git a/tests/phpunit/tests/rest-api/rest-schema-validation.php b/tests/phpunit/tests/rest-api/rest-schema-validation.php
    index df16a894ad..9c18a846dc 100644
    a b class WP_Test_REST_Schema_Validation extends WP_UnitTestCase { 
    296296                $this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
    297297                $this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
    298298        }
     299
     300        public function test_type_null() {
     301                $this->assertTrue( rest_validate_value_from_schema( null, array( 'type' => 'null' ) ) );
     302                $this->assertWPError( rest_validate_value_from_schema( '', array( 'type' => 'null' ) ) );
     303                $this->assertWPError( rest_validate_value_from_schema( 'null', array( 'type' => 'null' ) ) );
     304        }
     305
     306        public function test_nullable_date() {
     307                $schema = array(
     308                        'type'   => array( 'string', 'null' ),
     309                        'format' => 'date-time',
     310                );
     311
     312                $this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
     313                $this->assertTrue( rest_validate_value_from_schema( '2019-09-19T18:00:00', $schema ) );
     314                $this->assertWPError( rest_validate_value_from_schema( 'some random string', $schema ) );
     315        }
     316
     317        public function test_object_or_string() {
     318                $schema = array(
     319                        'type'       => array( 'object', 'string' ),
     320                        'properties' => array(
     321                                'raw' => array(
     322                                        'type' => 'string',
     323                                ),
     324                        ),
     325                );
     326
     327                $this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
     328                $this->assertTrue( rest_validate_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
     329                $this->assertWPError( rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema ) );
     330        }
    299331}
  • tests/qunit/fixtures/wp-api-generated.js

    diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js
    index 47f925fe44..fdbf056589 100644
    a b mockedApiResponse.Schema = { 
    373373                        "date": {
    374374                            "required": false,
    375375                            "description": "The date the object was published, in the site's timezone.",
    376                             "type": "string"
     376                            "type": [
     377                                "string",
     378                                "null"
     379                            ]
    377380                        },
    378381                        "date_gmt": {
    379382                            "required": false,
    380383                            "description": "The date the object was published, as GMT.",
    381                             "type": "string"
     384                            "type": [
     385                                "string",
     386                                "null"
     387                            ]
    382388                        },
    383389                        "slug": {
    384390                            "required": false,
    mockedApiResponse.Schema = { 
    553559                        "date": {
    554560                            "required": false,
    555561                            "description": "The date the object was published, in the site's timezone.",
    556                             "type": "string"
     562                            "type": [
     563                                "string",
     564                                "null"
     565                            ]
    557566                        },
    558567                        "date_gmt": {
    559568                            "required": false,
    560569                            "description": "The date the object was published, as GMT.",
    561                             "type": "string"
     570                            "type": [
     571                                "string",
     572                                "null"
     573                            ]
    562574                        },
    563575                        "slug": {
    564576                            "required": false,
    mockedApiResponse.Schema = { 
    893905                        "date": {
    894906                            "required": false,
    895907                            "description": "The date the object was published, in the site's timezone.",
    896                             "type": "string"
     908                            "type": [
     909                                "string",
     910                                "null"
     911                            ]
    897912                        },
    898913                        "date_gmt": {
    899914                            "required": false,
    900915                            "description": "The date the object was published, as GMT.",
    901                             "type": "string"
     916                            "type": [
     917                                "string",
     918                                "null"
     919                            ]
    902920                        },
    903921                        "slug": {
    904922                            "required": false,
    mockedApiResponse.Schema = { 
    12381256                        "date": {
    12391257                            "required": false,
    12401258                            "description": "The date the object was published, in the site's timezone.",
    1241                             "type": "string"
     1259                            "type": [
     1260                                "string",
     1261                                "null"
     1262                            ]
    12421263                        },
    12431264                        "date_gmt": {
    12441265                            "required": false,
    12451266                            "description": "The date the object was published, as GMT.",
    1246                             "type": "string"
     1267                            "type": [
     1268                                "string",
     1269                                "null"
     1270                            ]
    12471271                        },
    12481272                        "slug": {
    12491273                            "required": false,
    mockedApiResponse.Schema = { 
    13901414                        "date": {
    13911415                            "required": false,
    13921416                            "description": "The date the object was published, in the site's timezone.",
    1393                             "type": "string"
     1417                            "type": [
     1418                                "string",
     1419                                "null"
     1420                            ]
    13941421                        },
    13951422                        "date_gmt": {
    13961423                            "required": false,
    13971424                            "description": "The date the object was published, as GMT.",
    1398                             "type": "string"
     1425                            "type": [
     1426                                "string",
     1427                                "null"
     1428                            ]
    13991429                        },
    14001430                        "slug": {
    14011431                            "required": false,
    mockedApiResponse.Schema = { 
    17021732                        "date": {
    17031733                            "required": false,
    17041734                            "description": "The date the object was published, in the site's timezone.",
    1705                             "type": "string"
     1735                            "type": [
     1736                                "string",
     1737                                "null"
     1738                            ]
    17061739                        },
    17071740                        "date_gmt": {
    17081741                            "required": false,
    17091742                            "description": "The date the object was published, as GMT.",
    1710                             "type": "string"
     1743                            "type": [
     1744                                "string",
     1745                                "null"
     1746                            ]
    17111747                        },
    17121748                        "slug": {
    17131749                            "required": false,
    mockedApiResponse.Schema = { 
    20152051                        "date": {
    20162052                            "required": false,
    20172053                            "description": "The date the object was published, in the site's timezone.",
    2018                             "type": "string"
     2054                            "type": [
     2055                                "string",
     2056                                "null"
     2057                            ]
    20192058                        },
    20202059                        "date_gmt": {
    20212060                            "required": false,
    20222061                            "description": "The date the object was published, as GMT.",
    2023                             "type": "string"
     2062                            "type": [
     2063                                "string",
     2064                                "null"
     2065                            ]
    20242066                        },
    20252067                        "slug": {
    20262068                            "required": false,
    mockedApiResponse.Schema = { 
    21522194                        "date": {
    21532195                            "required": false,
    21542196                            "description": "The date the object was published, in the site's timezone.",
    2155                             "type": "string"
     2197                            "type": [
     2198                                "string",
     2199                                "null"
     2200                            ]
    21562201                        },
    21572202                        "date_gmt": {
    21582203                            "required": false,
    21592204                            "description": "The date the object was published, as GMT.",
    2160                             "type": "string"
     2205                            "type": [
     2206                                "string",
     2207                                "null"
     2208                            ]
    21612209                        },
    21622210                        "slug": {
    21632211                            "required": false,
    mockedApiResponse.Schema = { 
    23992447                        "date": {
    24002448                            "required": false,
    24012449                            "description": "The date the object was published, in the site's timezone.",
    2402                             "type": "string"
     2450                            "type": [
     2451                                "string",
     2452                                "null"
     2453                            ]
    24032454                        },
    24042455                        "date_gmt": {
    24052456                            "required": false,
    24062457                            "description": "The date the object was published, as GMT.",
    2407                             "type": "string"
     2458                            "type": [
     2459                                "string",
     2460                                "null"
     2461                            ]
    24082462                        },
    24092463                        "slug": {
    24102464                            "required": false,
    mockedApiResponse.Schema = { 
    25032557                        "date": {
    25042558                            "required": false,
    25052559                            "description": "The date the object was published, in the site's timezone.",
    2506                             "type": "string"
     2560                            "type": [
     2561                                "string",
     2562                                "null"
     2563                            ]
    25072564                        },
    25082565                        "date_gmt": {
    25092566                            "required": false,
    25102567                            "description": "The date the object was published, as GMT.",
    2511                             "type": "string"
     2568                            "type": [
     2569                                "string",
     2570                                "null"
     2571                            ]
    25122572                        },
    25132573                        "slug": {
    25142574                            "required": false,
    mockedApiResponse.Schema = { 
    26122672                        "date": {
    26132673                            "required": false,
    26142674                            "description": "The date the object was published, in the site's timezone.",
    2615                             "type": "string"
     2675                            "type": [
     2676                                "string",
     2677                                "null"
     2678                            ]
    26162679                        },
    26172680                        "date_gmt": {
    26182681                            "required": false,
    26192682                            "description": "The date the object was published, as GMT.",
    2620                             "type": "string"
     2683                            "type": [
     2684                                "string",
     2685                                "null"
     2686                            ]
    26212687                        },
    26222688                        "slug": {
    26232689                            "required": false,