diff --git a/src/wp-includes/collaboration/class-wp-sync-post-meta-storage.php b/src/wp-includes/collaboration/class-wp-sync-post-meta-storage.php index 7637cfa10f0bb..8a9d7c4c5426a 100644 --- a/src/wp-includes/collaboration/class-wp-sync-post-meta-storage.php +++ b/src/wp-includes/collaboration/class-wp-sync-post-meta-storage.php @@ -9,7 +9,7 @@ * Core class that provides an interface for storing and retrieving sync * updates and awareness data during a collaborative session. * - * Data is stored as post meta on a singleton post of a custom post type. + * Data is stored as post meta on a dedicated post per room of a custom post type. * * @since 7.0.0 * @@ -24,6 +24,22 @@ class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage { */ const POST_TYPE = 'wp_sync_storage'; + /** + * Meta key for awareness state. + * + * @since 7.0.0 + * @var string + */ + const AWARENESS_META_KEY = 'wp_sync_awareness'; + + /** + * Meta key for sync updates. + * + * @since 7.0.0 + * @var string + */ + const SYNC_UPDATE_META_KEY = 'wp_sync_update'; + /** * Cache of cursors by room. * @@ -41,12 +57,12 @@ class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage { private array $room_update_counts = array(); /** - * Singleton post ID for storing sync data. + * Cache of storage post IDs by room hash. * * @since 7.0.0 - * @var int|null + * @var array */ - private static ?int $storage_post_id = null; + private static array $storage_post_ids = array(); /** * Adds a sync update to a given room. @@ -58,20 +74,18 @@ class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage { * @return bool True on success, false on failure. */ public function add_update( string $room, $update ): bool { - $post_id = $this->get_storage_post_id(); + $post_id = $this->get_storage_post_id( $room ); if ( null === $post_id ) { return false; } - $meta_key = $this->get_room_meta_key( $room ); - // Create an envelope and stamp each update to enable cursor-based filtering. $envelope = array( 'timestamp' => $this->get_time_marker(), 'value' => $update, ); - return (bool) add_post_meta( $post_id, $meta_key, $envelope, false ); + return (bool) add_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, $envelope, false ); } /** @@ -85,13 +99,12 @@ public function add_update( string $room, $update ): bool { private function get_all_updates( string $room ): array { $this->room_cursors[ $room ] = $this->get_time_marker() - 100; // Small buffer to ensure consistency. - $post_id = $this->get_storage_post_id(); + $post_id = $this->get_storage_post_id( $room ); if ( null === $post_id ) { return array(); } - $meta_key = $this->get_room_meta_key( $room ); - $updates = get_post_meta( $post_id, $meta_key, false ); + $updates = get_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, false ); if ( ! is_array( $updates ) ) { $updates = array(); @@ -119,13 +132,12 @@ static function ( $update ): bool { * @return array Awareness state. */ public function get_awareness_state( string $room ): array { - $post_id = $this->get_storage_post_id(); + $post_id = $this->get_storage_post_id( $room ); if ( null === $post_id ) { return array(); } - $meta_key = $this->get_awareness_meta_key( $room ); - $awareness = get_post_meta( $post_id, $meta_key, true ); + $awareness = get_post_meta( $post_id, self::AWARENESS_META_KEY, true ); if ( ! is_array( $awareness ) ) { return array(); @@ -144,30 +156,16 @@ public function get_awareness_state( string $room ): array { * @return bool True on success, false on failure. */ public function set_awareness_state( string $room, array $awareness ): bool { - $post_id = $this->get_storage_post_id(); + $post_id = $this->get_storage_post_id( $room ); if ( null === $post_id ) { return false; } - $meta_key = $this->get_awareness_meta_key( $room ); - // update_post_meta returns false if the value is the same as the existing value. - update_post_meta( $post_id, $meta_key, $awareness ); + update_post_meta( $post_id, self::AWARENESS_META_KEY, $awareness ); return true; } - /** - * Gets the meta key for a room's awareness state. - * - * @since 7.0.0 - * - * @param string $room Room identifier. - * @return string Meta key. - */ - private function get_awareness_meta_key( string $room ): string { - return 'wp_sync_awareness_' . md5( $room ); - } - /** * Gets the current cursor for a given room. * @@ -185,60 +183,56 @@ public function get_cursor( string $room ): int { } /** - * Gets the meta key for a room's updates. - * - * @since 7.0.0 + * Gets or creates the storage post for a given room. * - * @param string $room Room identifier. - * @return string Meta key. - */ - private function get_room_meta_key( string $room ): string { - return 'wp_sync_update_' . md5( $room ); - } - - /** - * Gets or creates the singleton post for storing sync data. + * Each room gets its own dedicated post so that post meta cache + * invalidation is scoped to a single room rather than all of them. * * @since 7.0.0 * + * @param string $room Room identifier. * @return int|null Post ID. */ - private function get_storage_post_id(): ?int { - if ( is_int( self::$storage_post_id ) ) { - return self::$storage_post_id; + private function get_storage_post_id( string $room ): ?int { + $room_hash = md5( $room ); + + if ( isset( self::$storage_post_ids[ $room_hash ] ) ) { + return self::$storage_post_ids[ $room_hash ]; } - // Try to find an existing post. + // Try to find an existing post for this room. $posts = get_posts( array( 'post_type' => self::POST_TYPE, 'posts_per_page' => 1, 'post_status' => 'publish', + 'name' => $room_hash, 'fields' => 'ids', - 'order' => 'ASC', ) ); $post_id = array_first( $posts ); if ( is_int( $post_id ) ) { - self::$storage_post_id = $post_id; - return self::$storage_post_id; + self::$storage_post_ids[ $room_hash ] = $post_id; + return $post_id; } - // Create new post since none exists. + // Create new post for this room. $post_id = wp_insert_post( array( 'post_type' => self::POST_TYPE, 'post_status' => 'publish', 'post_title' => 'Sync Storage', + 'post_name' => $room_hash, ) ); if ( is_int( $post_id ) ) { - self::$storage_post_id = $post_id; + self::$storage_post_ids[ $room_hash ] = $post_id; + return $post_id; } - return self::$storage_post_id; + return null; } /** @@ -303,16 +297,15 @@ public function get_updates_after_cursor( string $room, int $cursor ): array { * @return bool True on success, false on failure. */ public function remove_updates_before_cursor( string $room, int $cursor ): bool { - $post_id = $this->get_storage_post_id(); + $post_id = $this->get_storage_post_id( $room ); if ( null === $post_id ) { return false; } $all_updates = $this->get_all_updates( $room ); - $meta_key = $this->get_room_meta_key( $room ); // Remove all updates for the room and re-store only those that are newer than the cursor. - if ( ! delete_post_meta( $post_id, $meta_key ) ) { + if ( ! delete_post_meta( $post_id, self::SYNC_UPDATE_META_KEY ) ) { return false; } @@ -320,7 +313,7 @@ public function remove_updates_before_cursor( string $room, int $cursor ): bool $add_result = true; foreach ( $all_updates as $envelope ) { if ( $add_result && $envelope['timestamp'] >= $cursor ) { - $add_result = (bool) add_post_meta( $post_id, $meta_key, $envelope, false ); + $add_result = (bool) add_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, $envelope, false ); } } diff --git a/tests/phpunit/tests/rest-api/rest-sync-server.php b/tests/phpunit/tests/rest-api/rest-sync-server.php index d6b5a6830e250..0180f02ca3b45 100644 --- a/tests/phpunit/tests/rest-api/rest-sync-server.php +++ b/tests/phpunit/tests/rest-api/rest-sync-server.php @@ -32,11 +32,11 @@ public function set_up() { parent::set_up(); // Reset storage post ID cache to ensure clean state after transaction rollback. - $reflection = new ReflectionProperty( 'WP_Sync_Post_Meta_Storage', 'storage_post_id' ); + $reflection = new ReflectionProperty( 'WP_Sync_Post_Meta_Storage', 'storage_post_ids' ); if ( PHP_VERSION_ID < 80100 ) { $reflection->setAccessible( true ); } - $reflection->setValue( null, null ); + $reflection->setValue( null, array() ); } /**