diff --git a/2meet-data-optimizer-hivepress-addon.php b/2meet-data-optimizer-hivepress-addon.php index dc9502b..d4f2216 100644 --- a/2meet-data-optimizer-hivepress-addon.php +++ b/2meet-data-optimizer-hivepress-addon.php @@ -3,7 +3,7 @@ * Plugin Name: 2meet Data Optimizer — Hivepress AddOn * Plugin URI: https://2meet.io/2meet-data-optimizer/hivepress * Description: HivePress + 12 擴充整合:13 adapter (core/reviews/bookings/messages/memberships/requests/favorites/statistics/tags/seo/social-links/blocks/marketplace) + 7 HPCT interceptor + 5 query interceptor + admin tab + CLI namespace - * Version: 0.5.2 + * Version: 0.5.3 * Requires at least: 6.0 * Tested up to: 6.9.4 * Requires PHP: 8.1 @@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -define( 'TMDO_HIVEPRESS_VERSION', '0.5.2' ); +define( 'TMDO_HIVEPRESS_VERSION', '0.5.3' ); define( 'TMDO_HIVEPRESS_PATH', plugin_dir_path( __FILE__ ) ); define( 'TMDO_HIVEPRESS_URL', plugin_dir_url( __FILE__ ) ); define( 'TMDO_HIVEPRESS_FILE', __FILE__ ); diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c034a..99fcd80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,20 @@ Versioning follows [Semantic Versioning](https://semver.org/). --- +## [0.5.3] — 2026-10-07 — 瀏覽數 flush 不再造成漂移 + +### Fixed + +- **`TMDO_Listing_Stats::flush_views_to_postmeta()` 寫完後同步扁平表與快取**:每小時把 warm zone 的瀏覽數 + 用原子 `UPDATE … meta_value + N` 加回 `hp_view_count`,繞過了 meta API:Hook Bus 看不到這次寫入, + dual_write/shadow_read 下 `hp_listing_core.hp_view_count` 每批落後一次(夜間 drift-check 每晚 diff), + 物件快取(Redis 下跨請求)也一直是舊值。現在 UPDATE 之後清掉該篇的 `post_meta` 快取, + 並在原生仍是真相時(同時寫原生與扁平)用 `TMDO_Entity_Migration_Engine::migrate_entity()` 重建那一列。 + `hp_view_count` 同時在 `hp_listing_core` 與 `hp_request_core`:只重建含這個 key、而且適用該篇 post type 的群組 + (不帶 post type 的 `get_field()` 只回最後登記的那組)。aeav_only 不動(扁平才是真相)。 + +--- + ## [0.5.2] — 2026-10-05 — attribute bridge 的 meta key ### Fixed diff --git a/includes/class-tmdo-listing-stats.php b/includes/class-tmdo-listing-stats.php index a17b955..612cadc 100644 --- a/includes/class-tmdo-listing-stats.php +++ b/includes/class-tmdo-listing-stats.php @@ -154,6 +154,8 @@ class TMDO_Listing_Stats { // Seed the key when it does not exist yet. if ( ! $updated ) { add_post_meta( $post_id, 'hp_view_count', $new_views, true ); + } else { + self::resync_view_count( $post_id ); } // Remove the warm entry after flush. @@ -164,6 +166,40 @@ class TMDO_Listing_Stats { return $flushed; } + /** + * The atomic UPDATE above bypasses the meta API: the object cache (persistent + * under Redis) keeps the old count, and the Hook Bus never sees the write, so in + * dual_write / shadow_read the flat column drifts by every flushed batch + * (drift-check diff on hp_view_count each night). Drop the cached meta and + * rebuild the entity's flat row from native — only while native is the source + * of truth (aeav_only keeps flat authoritative). + * + * @param int $post_id Listing post ID. + */ + private static function resync_view_count( int $post_id ): void { + wp_cache_delete( $post_id, 'post_meta' ); + if ( ! class_exists( 'TMDO_Mode_Manager' ) || ! class_exists( 'TMDO_Entity_Registry' ) || ! class_exists( 'TMDO_Entity_Migration_Engine' ) ) { + return; + } + if ( ! TMDO_Mode_Manager::writes_to_flat( 'post' ) || ! TMDO_Mode_Manager::writes_to_eav( 'post' ) ) { + return; + } + // hp_view_count lives in more than one group (hp_listing_core, hp_request_core): + // get_field() without a post type returns whichever registered last. Rebuild only + // the groups that hold the key and apply to this post's type. + $type = (string) get_post_type( $post_id ); + foreach ( TMDO_Entity_Registry::get_groups_for_type( 'post' ) as $group ) { + $types = TMDO_Entity_Registry::get_group_post_types( 'post', $group ); + if ( ! in_array( 'hp_view_count', TMDO_Entity_Registry::get_group_keys( 'post', $group ), true ) || ( $types && ! in_array( $type, $types, true ) ) ) { + continue; + } + TMDO_Entity_Migration_Engine::migrate_entity( 'post', $group, $post_id ); + if ( class_exists( 'TMDO_Cache_Orchestrator' ) ) { + TMDO_Cache_Orchestrator::invalidate( 'post', $post_id, $group ); + } + } + } + // ── Zone D: Expired Listing Archival ───────────────────────────────────── /**