WordPress.org

Make WordPress Core

Changeset 47943


Ignore:
Timestamp:
06/10/2020 02:20:18 AM (9 months ago)
Author:
TimothyBlynJacobs
Message:

REST API: Fix updating "multiple" meta keys with non-string values.

Previously, the REST API would end up deleting each row of metadata and recreating it unnecessarily. This was caused by a type mismatch where the metadata API would always return a string value, and the REST API operated on a typed value.

The REST API now applies the same sanitization and type casting for "multiple" meta keys and "single" meta keys.

Fixes #49339.
Props renathoc.

Location:
trunk
Files:
2 edited

Legend:

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

    r47858 r47943  
    270270        }
    271271
    272         $current = get_metadata( $meta_type, $object_id, $meta_key, false );
    273 
    274         $to_remove = $current;
     272        $current_values = get_metadata( $meta_type, $object_id, $meta_key, false );
     273        $subtype        = get_object_subtype( $meta_type, $object_id );
     274
     275        $to_remove = $current_values;
    275276        $to_add    = $values;
    276277
    277278        foreach ( $to_add as $add_key => $value ) {
    278             $remove_keys = array_keys( $to_remove, $value, true );
     279            $remove_keys = array_keys(
     280                array_filter(
     281                    $current_values,
     282                    function ( $stored_value ) use ( $meta_key, $subtype, $value ) {
     283                        return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value );
     284                    }
     285                )
     286            );
    279287
    280288            if ( empty( $remove_keys ) ) {
     
    360368        $old_value = get_metadata( $meta_type, $object_id, $meta_key );
    361369        $subtype   = get_object_subtype( $meta_type, $object_id );
    362         $args      = $this->get_registered_fields()[ $meta_key ];
    363 
    364         if ( 1 === count( $old_value ) ) {
    365             $sanitized = sanitize_meta( $meta_key, $value, $meta_type, $subtype );
    366 
    367             if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
    368                 // The return value of get_metadata will always be a string for scalar types.
    369                 $sanitized = (string) $sanitized;
    370             }
    371 
    372             if ( $sanitized === $old_value[0] ) {
    373                 return true;
    374             }
     370
     371        if ( 1 === count( $old_value ) && $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value ) ) {
     372            return true;
    375373        }
    376374
     
    388386
    389387        return true;
     388    }
     389
     390    /**
     391     * Checks if the user provided value is equivalent to a stored value for the given meta key.
     392     *
     393     * @since 5.5.0
     394     *
     395     * @param string $meta_key     The meta key being checked.
     396     * @param string $subtype      The object subtype.
     397     * @param mixed  $stored_value The currently stored value retrieved from get_metadata().
     398     * @param mixed  $user_value   The value provided by the user.
     399     * @return bool
     400     */
     401    protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) {
     402        $args      = $this->get_registered_fields()[ $meta_key ];
     403        $sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype );
     404
     405        if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
     406            // The return value of get_metadata will always be a string for scalar types.
     407            $sanitized = (string) $sanitized;
     408        }
     409
     410        return $sanitized === $stored_value;
    390411    }
    391412
  • trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    r47122 r47943  
    26572657
    26582658    /**
     2659     * @ticket 49339
     2660     */
     2661    public function test_update_multi_meta_value_handles_integer_types() {
     2662        $this->grant_write_permission();
     2663
     2664        register_post_meta(
     2665            'post',
     2666            'multi_integer',
     2667            array(
     2668                'type'         => 'integer',
     2669                'show_in_rest' => true,
     2670            )
     2671        );
     2672
     2673        $mid1 = add_post_meta( self::$post_id, 'multi_integer', 1 );
     2674        $mid2 = add_post_meta( self::$post_id, 'multi_integer', 2 );
     2675
     2676        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2677        $request->set_body_params(
     2678            array(
     2679                'meta' => array(
     2680                    'multi_integer' => array( 2, 3 ),
     2681                ),
     2682            )
     2683        );
     2684
     2685        $response = rest_get_server()->dispatch( $request );
     2686
     2687        $this->assertEquals( 200, $response->get_status() );
     2688        $this->assertEquals( array( 2, 3 ), $response->get_data()['meta']['multi_integer'] );
     2689
     2690        $this->assertFalse( get_metadata_by_mid( 'post', $mid1 ) );
     2691        $this->assertNotFalse( get_metadata_by_mid( 'post', $mid2 ) );
     2692    }
     2693
     2694    /**
     2695     * @ticket 49339
     2696     */
     2697    public function test_update_multi_meta_value_handles_boolean_types() {
     2698        $this->grant_write_permission();
     2699
     2700        register_post_meta(
     2701            'post',
     2702            'multi_boolean',
     2703            array(
     2704                'type'              => 'boolean',
     2705                'sanitize_callback' => 'absint',
     2706                'show_in_rest'      => true,
     2707            )
     2708        );
     2709
     2710        $mid1 = add_post_meta( self::$post_id, 'multi_boolean', 1 );
     2711        $mid2 = add_post_meta( self::$post_id, 'multi_boolean', 0 );
     2712
     2713        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2714        $request->set_body_params(
     2715            array(
     2716                'meta' => array(
     2717                    'multi_boolean' => array( 0 ),
     2718                ),
     2719            )
     2720        );
     2721
     2722        $response = rest_get_server()->dispatch( $request );
     2723
     2724        $this->assertEquals( 200, $response->get_status() );
     2725        $this->assertEquals( array( 0 ), $response->get_data()['meta']['multi_boolean'] );
     2726
     2727        $this->assertFalse( get_metadata_by_mid( 'post', $mid1 ) );
     2728        $this->assertNotFalse( get_metadata_by_mid( 'post', $mid2 ) );
     2729    }
     2730
     2731    /**
     2732     * @ticket 49339
     2733     */
     2734    public function test_update_multi_meta_value_handles_object_types() {
     2735        $this->grant_write_permission();
     2736
     2737        register_post_meta(
     2738            'post',
     2739            'multi_object',
     2740            array(
     2741                'type'         => 'object',
     2742                'show_in_rest' => array(
     2743                    'schema' => array(
     2744                        'type'       => 'object',
     2745                        'properties' => array(
     2746                            'a' => array(
     2747                                'type' => 'string',
     2748                            ),
     2749                        ),
     2750                    ),
     2751                ),
     2752            )
     2753        );
     2754
     2755        $mid1 = add_post_meta( self::$post_id, 'multi_object', array( 'a' => 'ant' ) );
     2756        $mid2 = add_post_meta( self::$post_id, 'multi_object', array( 'a' => 'anaconda' ) );
     2757
     2758        $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2759        $request->set_body_params(
     2760            array(
     2761                'meta' => array(
     2762                    'multi_object' => array(
     2763                        array( 'a' => 'anaconda' ),
     2764                        array( 'a' => 'alpaca' ),
     2765                    ),
     2766                ),
     2767            )
     2768        );
     2769
     2770        $response = rest_get_server()->dispatch( $request );
     2771
     2772        $this->assertEquals( 200, $response->get_status() );
     2773        $this->assertEquals(
     2774            array(
     2775                array( 'a' => 'anaconda' ),
     2776                array( 'a' => 'alpaca' ),
     2777            ),
     2778            $response->get_data()['meta']['multi_object']
     2779        );
     2780
     2781        $this->assertFalse( get_metadata_by_mid( 'post', $mid1 ) );
     2782        $this->assertNotFalse( get_metadata_by_mid( 'post', $mid2 ) );
     2783    }
     2784
     2785    /**
    26592786     * Internal function used to disable an insert query which
    26602787     * will trigger a wpdb error for testing purposes.
Note: See TracChangeset for help on using the changeset viewer.