Make WordPress Core


Ignore:
Timestamp:
09/28/2026 01:56:38 AM (18 hours ago)
Author:
peterwilsoncc
Message:

Options, Meta APIs: Ensure meta cache is an array before use.

In get_metadata_raw() and metadata_exists() ensure that cached data is of a valid form (ie, an array) before use. This prevents PHP errors of a third party has set the cache directly using a non array value.

Each of these functions now treat invalid cache forms as a cache miss. Invalid cache entries are deleted in update_meta_cache() if they are not an array.

Props josephscott, jonsurrell, westonruter.
Fixes #66091.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/meta.php

    r60253 r63958  
    130130                $this->assertFalse( metadata_exists( 'user', 1234567890, 'meta_key' ) );
    131131                remove_filter( 'get_user_metadata', '__return_zero' );
     132        }
     133
     134        /**
     135         * Non-array values that can not be used as a meta cache entry.
     136         *
     137         * @return array<string, array{mixed}>
     138         */
     139        public static function data_non_array_cache_values(): array {
     140                return array(
     141                        'object'  => array( new stdClass() ),
     142                        'string'  => array( 'meta_value' ),
     143                        'integer' => array( 1 ),
     144                        'float'   => array( 1.5 ),
     145                        'true'    => array( true ),
     146                );
     147        }
     148
     149        /**
     150         * @ticket 66091
     151         *
     152         * @dataProvider data_non_array_cache_values
     153         *
     154         * @param mixed $invalid_cached_value Unusable value to place in the meta cache.
     155         */
     156        public function test_metadata_exists_treats_non_array_cache_value_as_miss( $invalid_cached_value ): void {
     157                $this->assertTrue( wp_cache_set( self::$author->ID, $invalid_cached_value, 'user_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     158
     159                $this->assertTrue( metadata_exists( 'user', self::$author->ID, 'meta_key' ), 'An existing meta key should be reported as existing.' );
     160                $this->assertFalse( metadata_exists( 'user', self::$author->ID, 'foobarbaz' ), 'A missing meta key should be reported as not existing.' );
     161                $this->assertIsArray( wp_cache_get( self::$author->ID, 'user_meta' ), 'The unusable cache value should have been replaced.' );
     162        }
     163
     164        /**
     165         * @ticket 66091
     166         *
     167         * @dataProvider data_non_array_cache_values
     168         *
     169         * @param mixed $invalid_cached_value Unusable value to place in the meta cache.
     170         */
     171        public function test_get_metadata_treats_non_array_cache_value_as_miss( $invalid_cached_value ): void {
     172                $this->assertTrue( wp_cache_set( self::$author->ID, $invalid_cached_value, 'user_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     173
     174                $this->assertSame( 'meta_value', get_metadata( 'user', self::$author->ID, 'meta_key', true ), 'The single meta value should be returned.' );
     175                $this->assertSame( array( 'meta_value' ), get_metadata( 'user', self::$author->ID, 'meta_key' ), 'The array of meta values should be returned.' );
     176                $this->assertIsArray( wp_cache_get( self::$author->ID, 'user_meta' ), 'The unusable cache value should have been replaced.' );
     177        }
     178
     179        /**
     180         * @ticket 66091
     181         *
     182         * @dataProvider data_non_array_cache_values
     183         *
     184         * @param mixed $invalid_cached_value Unusable value to place in the meta cache.
     185         */
     186        public function test_get_metadata_with_empty_key_treats_non_array_cache_value_as_miss( $invalid_cached_value ): void {
     187                $this->assertTrue( wp_cache_set( self::$author->ID, $invalid_cached_value, 'user_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     188
     189                $meta = get_metadata( 'user', self::$author->ID );
     190
     191                $this->assertIsArray( $meta, 'All meta for the object should be returned as an array.' );
     192                $this->assertSame( array( 'meta_value' ), $meta['meta_key'], 'The existing meta key should be included in the returned meta.' );
     193        }
     194
     195        /**
     196         * @ticket 66091
     197         *
     198         * @dataProvider data_non_array_cache_values
     199         *
     200         * @param mixed $invalid_cached_value Unusable value to place in the meta cache.
     201         */
     202        public function test_update_meta_cache_replaces_non_array_cache_value( $invalid_cached_value ): void {
     203                $this->assertTrue( wp_cache_set( self::$author->ID, $invalid_cached_value, 'user_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     204
     205                $meta_cache = update_meta_cache( 'user', array( self::$author->ID ) );
     206
     207                $this->assertIsArray( $meta_cache );
     208                $this->assertArrayHasKey( self::$author->ID, $meta_cache );
     209                $this->assertIsArray( $meta_cache[ self::$author->ID ], 'The returned meta cache for the object should be an array.' );
     210                $this->assertSame( array( 'meta_value' ), $meta_cache[ self::$author->ID ]['meta_key'], 'The returned meta cache should include the existing meta key.' );
     211
     212                $cached = wp_cache_get( self::$author->ID, 'user_meta' );
     213                $this->assertIsArray( $cached, 'The unusable cache value should have been replaced.' );
     214                $this->assertSame( array( 'meta_value' ), $cached['meta_key'], 'The replaced cache value should include the existing meta key.' );
     215        }
     216
     217        /**
     218         * @ticket 66091
     219         */
     220        public function test_update_meta_cache_replaces_non_array_cache_value_for_object_without_meta(): void {
     221                $term_id = self::factory()->term->create();
     222
     223                $this->assertTrue( wp_cache_set( $term_id, new stdClass(), 'term_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     224
     225                $meta_cache = update_meta_cache( 'term', array( $term_id ) );
     226                $this->assertIsArray( $meta_cache );
     227                $this->assertArrayHasKey( $term_id, $meta_cache );
     228
     229                $this->assertSame( array(), $meta_cache[ $term_id ], 'The returned meta cache for an object without meta should be an empty array.' );
     230                $this->assertSame( array(), wp_cache_get( $term_id, 'term_meta' ), 'The unusable cache value should have been replaced with an empty array.' );
     231        }
     232
     233        /**
     234         * @ticket 66091
     235         */
     236        public function test_update_meta_cache_removes_non_array_cache_value_while_cache_addition_is_suspended(): void {
     237                $this->assertTrue( wp_cache_set( self::$author->ID, new stdClass(), 'user_meta' ), 'The unusable value should be placed in the cache, check test setup.' );
     238
     239                wp_suspend_cache_addition( true );
     240                $meta_cache = update_meta_cache( 'user', array( self::$author->ID ) );
     241                wp_suspend_cache_addition( false );
     242                $this->assertIsArray( $meta_cache );
     243                $this->assertIsArray( $meta_cache[ self::$author->ID ] );
     244
     245                $this->assertSame( array( 'meta_value' ), $meta_cache[ self::$author->ID ]['meta_key'], 'The meta should still be returned while cache addition is suspended.' );
     246                $this->assertFalse( wp_cache_get( self::$author->ID, 'user_meta' ), 'The unusable cache value should be removed but not replaced while cache addition is suspended.' );
    132247        }
    133248
Note: See TracChangeset for help on using the changeset viewer.