diff --git a/src/wp-includes/class-wp-meta-query.php b/src/wp-includes/class-wp-meta-query.php index 67e2d3d27ee0b..7649293306177 100644 --- a/src/wp-includes/class-wp-meta-query.php +++ b/src/wp-includes/class-wp-meta-query.php @@ -95,6 +95,22 @@ class WP_Meta_Query { */ protected $has_or_relation = false; + /** + * The type of object metadata is for (e.g. 'post', 'user', 'term'). + * + * @since 7.0.0 + * @var string + */ + protected $object_type = ''; + + /** + * Object subtypes for the query (e.g. post types). + * + * @since 7.0.0 + * @var string[] + */ + protected $object_subtypes = array(); + /** * Constructor. * @@ -364,6 +380,18 @@ public function get_sql( $type, $primary_table, $primary_id_column, $context = n $this->meta_table = $meta_table; $this->meta_id_column = sanitize_key( $type . '_id' ); + $this->object_type = $type; + + // Extract object subtypes from the parent query context. + $this->object_subtypes = array(); + if ( 'post' === $type && $context instanceof WP_Query && ! empty( $context->query_vars['post_type'] ) ) { + $post_type = $context->query_vars['post_type']; + if ( is_array( $post_type ) ) { + $this->object_subtypes = $post_type; + } elseif ( 'any' !== $post_type ) { + $this->object_subtypes = array( $post_type ); + } + } $this->primary_table = $primary_table; $this->primary_id_column = $primary_id_column; @@ -533,6 +561,28 @@ protected function get_sql_for_query( &$query, $depth = 0 ) { public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { global $wpdb; + // Refuse to query by a post meta key registered as non-query-cacheable. + if ( 'post' === $this->object_type + && isset( $clause['key'] ) + && is_string( $clause['key'] ) + && $this->is_non_cacheable_meta_key( $clause['key'] ) + ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: The meta key. */ + __( 'The meta key "%s" is registered with invalidates_query_cache set to false and cannot be used in meta queries.' ), + $clause['key'] + ), + '7.0.0' + ); + + return array( + 'join' => array(), + 'where' => array(), + ); + } + $sql_chunks = array( 'where' => array(), 'join' => array(), @@ -726,6 +776,21 @@ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) } } + // When querying by meta_value without a specific key, exclude + // non-cacheable meta keys so their rows never appear in results. + // This is necessary because cache invalidation is skipped for these + // keys, so including them would produce stale cached query results. + if ( 'post' === $this->object_type + && ! array_key_exists( 'key', $clause ) + && array_key_exists( 'value', $clause ) + ) { + $excluded_keys = $this->get_non_cacheable_meta_keys(); + if ( ! empty( $excluded_keys ) ) { + $placeholders = implode( ',', array_fill( 0, count( $excluded_keys ), '%s' ) ); + $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key NOT IN ($placeholders)", $excluded_keys ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + } + // meta_value. if ( array_key_exists( 'value', $clause ) ) { $meta_value = $clause['value']; @@ -807,6 +872,71 @@ public function get_clauses() { return $this->clauses; } + /** + * Checks whether a meta key is registered as non-query-cacheable. + * + * This check is deliberately conservative: it only refuses a meta key + * when the post type is known from the parent WP_Query context. When + * no post type context is available (e.g. direct WP_Meta_Query usage + * without a WP_Query, or post_type set to 'any'), the key is allowed + * through. This avoids incorrectly blocking legitimate queries. + * + * Correctness is guaranteed by the cache invalidation side + * (wp_cache_set_posts_last_changed), which always has the post type + * from the object ID. That side is authoritative — it ensures + * non-cacheable meta never invalidates query caches. This query-side + * check is a safety net to warn developers and prevent obviously + * broken queries, not the primary enforcement mechanism. + * + * When multiple post types are queried, the key is refused if any + * of them has it registered as non-cacheable. + * + * @since 7.0.0 + * + * @param string $meta_key Meta key to check. + * @return bool True if the meta key is non-cacheable, false otherwise. + */ + protected function is_non_cacheable_meta_key( $meta_key ) { + if ( empty( $this->object_subtypes ) ) { + return false; + } + + foreach ( $this->object_subtypes as $subtype ) { + if ( ! wp_post_meta_invalidates_query_cache( $meta_key, $subtype ) ) { + return true; + } + } + + return false; + } + + /** + * Returns all post meta keys registered with invalidates_query_cache false. + * + * @since 7.0.0 + * + * @return string[] Array of non-cacheable meta keys. + */ + protected function get_non_cacheable_meta_keys() { + global $wp_meta_keys; + + $keys = array(); + + if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys['post'] ) ) { + return $keys; + } + + foreach ( $wp_meta_keys['post'] as $registered_keys ) { + foreach ( $registered_keys as $meta_key => $args ) { + if ( isset( $args['invalidates_query_cache'] ) && ! $args['invalidates_query_cache'] ) { + $keys[] = $meta_key; + } + } + } + + return array_unique( $keys ); + } + /** * Identifies an existing table alias that is compatible with the current * query clause. diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index a1c2e4d93df87..e53580093bd4f 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -122,9 +122,9 @@ } // Post meta. -add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); -add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); -add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed' ); +add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed', 10, 3 ); +add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed', 10, 3 ); +add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed', 10, 3 ); // User meta. add_action( 'added_user_meta', 'wp_cache_set_users_last_changed' ); diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index c657c6c2e7af3..ec687568469af 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -1440,16 +1440,17 @@ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { } $defaults = array( - 'object_subtype' => '', - 'type' => 'string', - 'label' => '', - 'description' => '', - 'default' => '', - 'single' => false, - 'sanitize_callback' => null, - 'auth_callback' => null, - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'object_subtype' => '', + 'type' => 'string', + 'label' => '', + 'description' => '', + 'default' => '', + 'single' => false, + 'sanitize_callback' => null, + 'auth_callback' => null, + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ); // There used to be individual args for sanitize and auth callbacks. @@ -1508,6 +1509,12 @@ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { } } + if ( ! $args['invalidates_query_cache'] && 'post' !== $object_type ) { + _doing_it_wrong( __FUNCTION__, __( 'The invalidates_query_cache parameter is only supported for post meta.' ), '7.0.0' ); + + return false; + } + // If `auth_callback` is not provided, fall back to `is_protected_meta()`. if ( empty( $args['auth_callback'] ) ) { if ( is_protected_meta( $meta_key, $object_type ) ) { @@ -1644,6 +1651,41 @@ function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = return isset( $meta_keys[ $meta_key ] ); } +/** + * Checks whether a post meta key invalidates query caches when updated. + * + * Post meta keys registered with `'invalidates_query_cache' => false` will not + * cause query cache invalidation when added, updated, or deleted. This is + * useful for high-frequency post meta that is never used in query filters, such + * as real-time collaboration sync data. + * + * Unregistered meta keys are assumed to invalidate query caches. + * + * @since 7.0.0 + * + * @param string $meta_key Metadata key. + * @param string $post_type Post type to check. + * @return bool True if the meta key invalidates query caches, false otherwise. + */ +function wp_post_meta_invalidates_query_cache( $meta_key, $post_type ) { + // Check keys registered for this specific post type. + $meta_keys = get_registered_meta_keys( 'post', $post_type ); + + if ( isset( $meta_keys[ $meta_key ] ) ) { + return $meta_keys[ $meta_key ]['invalidates_query_cache']; + } + + // Also check keys registered for all post types. + $meta_keys = get_registered_meta_keys( 'post' ); + + if ( isset( $meta_keys[ $meta_key ] ) ) { + return $meta_keys[ $meta_key ]['invalidates_query_cache']; + } + + // Unregistered keys default to invalidating caches. + return true; +} + /** * Unregisters a meta key from the list of registered keys. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 90b6c5e9e93e5..63269238a1971 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -690,6 +690,22 @@ function create_initial_post_types() { 'supports' => array( 'custom-fields' ), ) ); + + register_post_meta( + '', + 'wp_sync_awareness', + array( + 'invalidates_query_cache' => false, + ) + ); + + register_post_meta( + '', + 'wp_sync_update', + array( + 'invalidates_query_cache' => false, + ) + ); } register_post_status( @@ -8440,9 +8456,31 @@ function wp_add_trashed_suffix_to_post_name_for_post( $post ) { /** * Sets the last changed time for the 'posts' cache group. * + * When called from a post meta action (`added_post_meta`, `updated_post_meta`, + * or `deleted_post_meta`), the meta key is checked against the registered meta + * registry. If the meta key was registered with `'invalidates_query_cache' => false`, + * the cache is not invalidated. This allows high-frequency meta writes (e.g. + * real-time collaboration data) to avoid invalidating query caches site-wide. + * + * This is the authoritative check — meta actions always provide both the + * object ID and meta key, so the exact post type is always known. This + * guarantees that non-cacheable meta never incorrectly invalidates caches, + * regardless of how the meta key was registered (globally or per post type). + * * @since 5.0.0 + * @since 7.0.0 Added the `$meta_id`, `$object_id`, and `$meta_key` parameters. + * + * @param int $meta_id Optional. Meta ID. Passed by meta action hooks. + * @param int $object_id Optional. Object ID. Passed by meta action hooks. + * @param string $meta_key Optional. Meta key. Passed by meta action hooks. */ -function wp_cache_set_posts_last_changed() { +function wp_cache_set_posts_last_changed( $meta_id = 0, $object_id = 0, $meta_key = '' ) { + if ( $meta_key && $object_id ) { + $post_type = get_post_type( $object_id ); + if ( $post_type && ! wp_post_meta_invalidates_query_cache( $meta_key, $post_type ) ) { + return; + } + } wp_cache_set_last_changed( 'posts' ); } diff --git a/tests/phpunit/tests/meta/invalidatesQueryCache.php b/tests/phpunit/tests/meta/invalidatesQueryCache.php new file mode 100644 index 0000000000000..7a51c576543c8 --- /dev/null +++ b/tests/phpunit/tests/meta/invalidatesQueryCache.php @@ -0,0 +1,432 @@ +post->create(); + } + + public static function wpTearDownAfterClass() { + wp_delete_post( self::$post_id, true ); + } + + public function tear_down() { + unregister_meta_key( 'post', 'nocache_meta' ); + unregister_meta_key( 'post', 'nocache_meta', 'post' ); + unregister_meta_key( 'post', 'nocache_meta', 'page' ); + unregister_meta_key( 'post', 'normal_meta' ); + parent::tear_down(); + } + + /** + * The `invalidates_query_cache` argument should default to true. + */ + public function test_default_value_is_true() { + register_post_meta( '', 'normal_meta', array() ); + + $meta_keys = get_registered_meta_keys( 'post' ); + $this->assertTrue( $meta_keys['normal_meta']['invalidates_query_cache'] ); + } + + /** + * The `invalidates_query_cache` argument should be stored when set to false. + */ + public function test_registered_as_false() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $meta_keys = get_registered_meta_keys( 'post' ); + $this->assertFalse( $meta_keys['nocache_meta']['invalidates_query_cache'] ); + } + + /** + * Adding post meta for a non-cacheable key should not bump last_changed. + */ + public function test_add_post_meta_does_not_invalidate_cache() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + // Prime the last_changed value. + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + add_post_meta( self::$post_id, 'nocache_meta', 'value1' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertSame( $before, $after, 'last_changed should not change for non-cacheable meta.' ); + } + + /** + * Updating post meta for a non-cacheable key should not bump last_changed. + */ + public function test_update_post_meta_does_not_invalidate_cache() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + add_post_meta( self::$post_id, 'nocache_meta', 'value1' ); + + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + update_post_meta( self::$post_id, 'nocache_meta', 'value2' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertSame( $before, $after, 'last_changed should not change for non-cacheable meta.' ); + } + + /** + * Deleting post meta for a non-cacheable key should not bump last_changed. + */ + public function test_delete_post_meta_does_not_invalidate_cache() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + add_post_meta( self::$post_id, 'nocache_meta', 'value1' ); + + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + delete_post_meta( self::$post_id, 'nocache_meta' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertSame( $before, $after, 'last_changed should not change for non-cacheable meta.' ); + } + + /** + * Regular meta should still invalidate the cache as before. + */ + public function test_regular_meta_still_invalidates_cache() { + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + // Small sleep to ensure microtime differs. + usleep( 1000 ); + add_post_meta( self::$post_id, 'regular_unregistered_meta', 'value1' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertNotSame( $before, $after, 'last_changed should change for regular meta.' ); + } + + /** + * Meta registered with invalidates_query_cache true should still invalidate. + */ + public function test_registered_cacheable_meta_still_invalidates_cache() { + register_post_meta( + '', + 'normal_meta', + array( 'invalidates_query_cache' => true ) + ); + + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + add_post_meta( self::$post_id, 'normal_meta', 'value1' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertNotSame( $before, $after, 'last_changed should change for cacheable meta.' ); + } + + /** + * WP_Meta_Query should refuse to query by a non-cacheable meta key + * when the WP_Query context provides a post type. + * + * @expectedIncorrectUsage WP_Meta_Query::get_sql_for_clause + */ + public function test_meta_query_refuses_non_cacheable_key() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $query = new WP_Query(); + $query->query_vars['post_type'] = 'post'; + + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'nocache_meta', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID', $query ); + + $this->assertStringNotContainsString( 'nocache_meta', $sql['where'], 'Non-cacheable meta key should not appear in WHERE clause.' ); + $this->assertStringNotContainsString( 'nocache_meta', $sql['join'], 'Non-cacheable meta key should not appear in JOIN clause.' ); + } + + /** + * WP_Meta_Query should allow a non-cacheable meta key when no WP_Query + * context is available. Without knowing the post type, refusing the query + * could incorrectly block legitimate usage. Cache correctness is still + * guaranteed by the invalidation side, which always knows the post type. + */ + public function test_meta_query_allows_non_cacheable_key_without_context() { + register_post_meta( + '', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'nocache_meta', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID' ); + + $this->assertStringContainsString( 'nocache_meta', $sql['where'], 'Non-cacheable meta key should be allowed without WP_Query context.' ); + } + + /** + * WP_Meta_Query should work normally for regular meta keys. + */ + public function test_meta_query_allows_regular_key() { + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'some_regular_key', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID' ); + + $this->assertStringContainsString( 'some_regular_key', $sql['where'], 'Regular meta key should appear in WHERE clause.' ); + } + + /** + * A key registered for a specific post type should skip cache invalidation. + */ + public function test_subtype_registration_skips_cache_invalidation() { + register_post_meta( + 'post', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + add_post_meta( self::$post_id, 'nocache_meta', 'value1' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertSame( $before, $after, 'last_changed should not change for non-cacheable meta registered on a specific post type.' ); + } + + /** + * A key registered for a specific post type should be refused in meta queries + * when the WP_Query targets that post type. + * + * @expectedIncorrectUsage WP_Meta_Query::get_sql_for_clause + */ + public function test_subtype_registration_refuses_meta_query_for_matching_post_type() { + register_post_meta( + 'post', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $query = new WP_Query(); + $query->query_vars['post_type'] = 'post'; + + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'nocache_meta', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID', $query ); + + $this->assertStringNotContainsString( 'nocache_meta', $sql['where'], 'Non-cacheable meta key should not appear in WHERE clause for matching post type.' ); + } + + /** + * A key registered as non-cacheable for one post type should be allowed + * in meta queries targeting a different post type. The query-side check + * is conservative — it only refuses when the post type is known to be + * non-cacheable. Cache correctness for the other post type is enforced + * by the invalidation side. + */ + public function test_subtype_registration_allows_meta_query_for_different_post_type() { + register_post_meta( + 'page', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $query = new WP_Query(); + $query->query_vars['post_type'] = 'post'; + + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'nocache_meta', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID', $query ); + + $this->assertStringContainsString( 'nocache_meta', $sql['where'], 'Meta key should be allowed for a post type where it is cacheable.' ); + } + + /** + * When querying multiple post types, if the key is non-cacheable on any + * of them, the clause should be refused. + * + * @expectedIncorrectUsage WP_Meta_Query::get_sql_for_clause + */ + public function test_subtype_registration_refuses_meta_query_for_array_with_matching_post_type() { + register_post_meta( + 'page', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + $query = new WP_Query(); + $query->query_vars['post_type'] = array( 'post', 'page' ); + + $meta_query = new WP_Meta_Query( + array( + array( + 'key' => 'nocache_meta', + 'value' => 'test', + ), + ) + ); + + $sql = $meta_query->get_sql( 'post', 'wp_posts', 'ID', $query ); + + $this->assertStringNotContainsString( 'nocache_meta', $sql['where'], 'Non-cacheable meta key should be refused when any queried post type has it as non-cacheable.' ); + } + + /** + * A key registered as non-cacheable for one post type should still + * invalidate the cache when written to a different post type. The + * invalidation side always knows the exact post type from the object ID, + * so per-post-type behavior is always correct. + */ + public function test_subtype_registration_does_not_skip_cache_for_different_post_type() { + register_post_meta( + 'page', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + // self::$post_id is a 'post', not a 'page'. + wp_cache_set_last_changed( 'posts' ); + $before = wp_cache_get_last_changed( 'posts' ); + + usleep( 1000 ); + add_post_meta( self::$post_id, 'nocache_meta', 'value1' ); + + $after = wp_cache_get_last_changed( 'posts' ); + $this->assertNotSame( $before, $after, 'last_changed should still change when writing to a post type where the key is cacheable.' ); + } + + /** + * A WP_Query using meta_value without meta_key should not return posts + * matched via a non-cacheable meta key. + */ + public function test_wp_query_meta_value_excludes_non_cacheable_keys() { + register_post_meta( + 'post', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + add_post_meta( self::$post_id, 'nocache_meta', 'nocache_value' ); + + $query = new WP_Query( + array( + 'fields' => 'ids', + 'meta_value' => 'nocache_value', + ) + ); + + $this->assertEmpty( $query->posts, 'WP_Query by meta_value should not match non-cacheable meta keys.' ); + } + + /** + * A WP_Query using meta_value without meta_key should not return stale + * cached results after non-cacheable meta is deleted. + */ + public function test_wp_query_meta_value_no_stale_cache_after_delete() { + register_post_meta( + 'post', + 'nocache_meta', + array( 'invalidates_query_cache' => false ) + ); + + add_post_meta( self::$post_id, 'nocache_meta', 'nocache_value' ); + + // Run the query once to prime the cache. + $query1 = new WP_Query( + array( + 'fields' => 'ids', + 'meta_value' => 'nocache_value', + ) + ); + + delete_post_meta( self::$post_id, 'nocache_meta' ); + + $query2 = new WP_Query( + array( + 'fields' => 'ids', + 'meta_value' => 'nocache_value', + ) + ); + + $this->assertEmpty( $query2->posts, 'WP_Query should not return stale cached results for non-cacheable meta.' ); + } +} diff --git a/tests/phpunit/tests/meta/registerMeta.php b/tests/phpunit/tests/meta/registerMeta.php index 30b6920bdea0d..62dbe982ef491 100644 --- a/tests/phpunit/tests/meta/registerMeta.php +++ b/tests/phpunit/tests/meta/registerMeta.php @@ -91,14 +91,15 @@ public function test_register_meta_with_post_object_type_populates_wp_meta_keys( 'post' => array( '' => array( 'flight_number' => array( - 'type' => 'string', - 'label' => '', - 'description' => '', - 'single' => false, - 'sanitize_callback' => null, - 'auth_callback' => '__return_true', - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'type' => 'string', + 'label' => '', + 'description' => '', + 'single' => false, + 'sanitize_callback' => null, + 'auth_callback' => '__return_true', + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), ), ), @@ -117,14 +118,15 @@ public function test_register_meta_with_term_object_type_populates_wp_meta_keys( 'term' => array( '' => array( 'category_icon' => array( - 'type' => 'string', - 'label' => '', - 'description' => '', - 'single' => false, - 'sanitize_callback' => null, - 'auth_callback' => '__return_true', - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'type' => 'string', + 'label' => '', + 'description' => '', + 'single' => false, + 'sanitize_callback' => null, + 'auth_callback' => '__return_true', + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), ), ), @@ -173,14 +175,15 @@ public function test_register_meta_with_current_sanitize_callback_populates_wp_m 'post' => array( '' => array( 'flight_number' => array( - 'type' => 'string', - 'label' => '', - 'description' => '', - 'single' => false, - 'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ), - 'auth_callback' => '__return_true', - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'type' => 'string', + 'label' => '', + 'description' => '', + 'single' => false, + 'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ), + 'auth_callback' => '__return_true', + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), ), ), @@ -355,14 +358,15 @@ public function test_register_meta_with_subtype_populates_wp_meta_keys( $type, $ $type => array( $subtype => array( 'flight_number' => array( - 'type' => 'string', - 'label' => '', - 'description' => '', - 'single' => false, - 'sanitize_callback' => null, - 'auth_callback' => '__return_true', - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'type' => 'string', + 'label' => '', + 'description' => '', + 'single' => false, + 'sanitize_callback' => null, + 'auth_callback' => '__return_true', + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), ), ), @@ -410,14 +414,15 @@ public function test_unregister_meta_without_subtype_keeps_subtype_meta_key( $ty $type => array( $subtype => array( 'flight_number' => array( - 'type' => 'string', - 'label' => '', - 'description' => '', - 'single' => false, - 'sanitize_callback' => null, - 'auth_callback' => '__return_true', - 'show_in_rest' => false, - 'revisions_enabled' => false, + 'type' => 'string', + 'label' => '', + 'description' => '', + 'single' => false, + 'sanitize_callback' => null, + 'auth_callback' => '__return_true', + 'show_in_rest' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), ), ), diff --git a/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php b/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php index a45b015ad9728..79a07ce7a244e 100644 --- a/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php +++ b/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php @@ -30,13 +30,13 @@ public function test_should_register_persisted_preferences_meta() { // Test to detect changes in meta key structure. $this->assertSame( array( - 'type' => 'object', - 'label' => '', - 'description' => '', - 'single' => true, - 'sanitize_callback' => null, - 'auth_callback' => '__return_true', - 'show_in_rest' => array( + 'type' => 'object', + 'label' => '', + 'description' => '', + 'single' => true, + 'sanitize_callback' => null, + 'auth_callback' => '__return_true', + 'show_in_rest' => array( 'name' => 'persisted_preferences', 'type' => 'object', 'schema' => array( @@ -53,7 +53,8 @@ public function test_should_register_persisted_preferences_meta() { 'additionalProperties' => true, ), ), - 'revisions_enabled' => false, + 'revisions_enabled' => false, + 'invalidates_query_cache' => true, ), $wp_meta_keys['user'][''][ $meta_key ], 'The registered metadata did not have the expected structure'