As the sun rises and the forest mist clears, and the clouds return and the caves darken, these changes of light and shadow are the morning and evening in the mountains. Wildflowers bloom with their subtle fragrance, fine trees flourish with their dense shade, the wind and frost are pure and clean, and the water recedes to reveal the rocks—these are the four seasons in the mountains. Going out in the morning and returning in the evening, the scenery of the four seasons is different, and the joy is endless.至于负者歌于途,行者休于树,前者呼,后者应,伛偻提携,往来而不绝者,滁人游也。临溪而渔,溪深而鱼肥,酿泉为酒,泉香而酒洌,山肴野蔌,杂然而前陈者,太守宴也。宴酣之乐,非丝非竹,射者中,弈者胜,觥筹交错,起坐而喧哗者,众宾欢也。苍颜白发,颓然乎其间者,太守醉也。
<?php
/**
* WP_Sync_Post_Meta_Storage class
*
* @package WordPress
*/
/**
* 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 dedicated post per room of a custom post type.
*
* @since 7.0.0
*
* @access private
*/
class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage {
/**
* Post type for sync storage.
*
* @since 7.0.0
* @var string
*/
const POST_TYPE = 'wp_sync_storage';
/**
* Meta key for awareness state.
*
* @since 7.0.0
* @var string
*/
const AWARENESS_META_KEY = 'wp_sync_awareness_state';
/**
* Meta key for sync updates.
*
* @since 7.0.0
* @var string
*/
const SYNC_UPDATE_META_KEY = 'wp_sync_update_data';
/**
* Cache of cursors by room.
*
* @since 7.0.0
* @var array<string, int>
*/
private array $room_cursors = array();
/**
* Cache of update counts by room.
*
* @since 7.0.0
* @var array<string, int>
*/
private array $room_update_counts = array();
/**
* Cache of storage post IDs by room hash.
*
* @since 7.0.0
* @var array<string, int>
*/
private static array $storage_post_ids = array();
/**
* Adds a sync update to a given room.
*
* @since 7.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @param mixed $update Sync update.
* @return bool True on success, false on failure.
*/
public function add_update( string $room, $update ): bool {
global $wpdb;
$post_id = $this->get_storage_post_id( $room );
if ( null === $post_id ) {
return false;
}
// Use direct database operation to avoid cache invalidation performed by
// post meta functions (`wp_cache_set_posts_last_changed()` and direct
// `wp_cache_delete()` calls).
return (bool) $wpdb->insert(
$wpdb->postmeta,
array(
'post_id' => $post_id,
'meta_key' => self::SYNC_UPDATE_META_KEY,
'meta_value' => wp_json_encode( $update ),
),
array( '%d', '%s', '%s' )
);
}
/**
* Gets awareness state for a given room.
*
* @since 7.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @return array<int, mixed> Awareness state.
*/
public function get_awareness_state( string $room ): array {
global $wpdb;
$post_id = $this->get_storage_post_id( $room );
if ( null === $post_id ) {
return array();
}
// Use direct database operation to avoid updating the post meta cache.
// ORDER BY meta_id DESC ensures the latest row wins if duplicates exist
// from a past race condition in set_awareness_state().
$meta_value = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ORDER BY meta_id DESC LIMIT 1",
$post_id,
self::AWARENESS_META_KEY
)
);
if ( null === $meta_value ) {
return array();
}
$awareness = json_decode( $meta_value, true );
if ( ! is_array( $awareness ) ) {
return array();
}
return array_values( $awareness );
}
/**
* Sets awareness state for a given room.
*
* @since 7.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @param array<int, mixed> $awareness Serializable awareness state.
* @return bool True on success, false on failure.
*/
public function set_awareness_state( string $room, array $awareness ): bool {
global $wpdb;
$post_id = $this->get_storage_post_id( $room );
if ( null === $post_id ) {
return false;
}
// Use direct database operation to avoid cache invalidation performed by
// post meta functions (`wp_cache_set_posts_last_changed()` and direct
// `wp_cache_delete()` calls).
//
// If two concurrent requests both see no row and both INSERT, the
// duplicate is harmless: get_awareness_state() reads the latest row
// (ORDER BY meta_id DESC).
$meta_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ORDER BY meta_id DESC LIMIT 1",
$post_id,
self::AWARENESS_META_KEY
)
);
if ( $meta_id ) {
return (bool) $wpdb->update(
$wpdb->postmeta,
array( 'meta_value' => wp_json_encode( $awareness ) ),
array( 'meta_id' => $meta_id ),
array( '%s' ),
array( '%d' )
);
}
return (bool) $wpdb->insert(
$wpdb->postmeta,
array(
'post_id' => $post_id,
'meta_key' => self::AWARENESS_META_KEY,
'meta_value' => wp_json_encode( $awareness ),
),
array( '%d', '%s', '%s' )
);
}
/**
* Gets the current cursor for a given room.
*
* The cursor is set during get_updates_after_cursor() and represents the
* highest meta_id seen for the room's sync updates.
*
* @since 7.0.0
*
* @param string $room Room identifier.
* @return int Current cursor for the room.
*/
public function get_cursor( string $room ): int {
return $this->room_cursors[ $room ] ?? 0;
}
/**
* Gets or creates the storage post for a given room.
*
* 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( 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 for this room.
$posts = get_posts(
array(
'post_type' => self::POST_TYPE,
'posts_per_page' => 1,
'post_status' => 'publish',
'name' => $room_hash,
'fields' => 'ids',
'orderby' => 'ID',
'order' => 'ASC',
)
);
$post_id = array_first( $posts );
if ( is_int( $post_id ) ) {
self::$storage_post_ids[ $room_hash ] = $post_id;
return $post_id;
}
// 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_ids[ $room_hash ] = $post_id;
return $post_id;
}
return null;
}
/**
* Gets the number of updates stored for a given room.
*
* @since 7.0.0
*
* @param string $room Room identifier.
* @return int Number of updates stored for the room.
*/
public function get_update_count( string $room ): int {
return $this->room_update_counts[ $room ] ?? 0;
}
/**
* Retrieves sync updates from a room after the given cursor.
*
* @since 7.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @param int $cursor Return updates after this cursor (meta_id).
* @return array<int, mixed> Sync updates.
*/
public function get_updates_after_cursor( string $room, int $cursor ): array {
global $wpdb;
$post_id = $this->get_storage_post_id( $room );
if ( null === $post_id ) {
$this->room_cursors[ $room ] = 0;
$this->room_update_counts[ $room ] = 0;
return array();
}
// Capture the current room state first so the returned cursor is race-safe.
$stats = $wpdb->get_row(
$wpdb->prepare(
"SELECT COUNT(*) AS total_updates, COALESCE( MAX(meta_id), 0 ) AS max_meta_id FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
$post_id,
self::SYNC_UPDATE_META_KEY
)
);
$total_updates = $stats ? (int) $stats->total_updates : 0;
$max_meta_id = $stats ? (int) $stats->max_meta_id : 0;
$this->room_update_counts[ $room ] = $total_updates;
$this->room_cursors[ $room ] = $max_meta_id;
if ( $max_meta_id <= $cursor ) {
return array();
}
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_id > %d AND meta_id <= %d ORDER BY meta_id ASC",
$post_id,
self::SYNC_UPDATE_META_KEY,
$cursor,
$max_meta_id
)
);
if ( ! $rows ) {
return array();
}
$updates = array();
foreach ( $rows as $row ) {
$decoded = json_decode( $row->meta_value, true );
if ( null !== $decoded ) {
$updates[] = $decoded;
}
}
return $updates;
}
/**
* Removes updates from a room that are older than the given cursor.
*
* @since 7.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @param int $cursor Remove updates with meta_id < this cursor.
* @return bool True on success, false on failure.
*/
public function remove_updates_before_cursor( string $room, int $cursor ): bool {
global $wpdb;
$post_id = $this->get_storage_post_id( $room );
if ( null === $post_id ) {
return false;
}
$deleted_rows = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_id < %d",
$post_id,
self::SYNC_UPDATE_META_KEY,
$cursor
)
);
if ( false === $deleted_rows ) {
return false;
}
return true;
}
}