Merge pull request 'release 0.5.3: 2meet-data-optimizer-hivepress-addon' (#7) from release/0.5.3 into main
Tests / PHP Lint (push) Successful in 6s
Tests / Unit Tests (push) Successful in 1m6s
Tests / Integration Tests (push) Successful in 1m6s

This commit was merged in pull request #7.
This commit is contained in:
2026-10-07 14:02:51 +08:00
5 changed files with 66 additions and 5 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
* Plugin Name: 2meet Data Optimizer — Hivepress AddOn * Plugin Name: 2meet Data Optimizer — Hivepress AddOn
* Plugin URI: https://2meet.io/2meet-data-optimizer/hivepress * 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 * 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.1 * Version: 0.5.3
* Requires at least: 6.0 * Requires at least: 6.0
* Tested up to: 6.9.4 * Tested up to: 6.9.4
* Requires PHP: 8.1 * Requires PHP: 8.1
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
define( 'TMDO_HIVEPRESS_VERSION', '0.5.1' ); define( 'TMDO_HIVEPRESS_VERSION', '0.5.3' );
define( 'TMDO_HIVEPRESS_PATH', plugin_dir_path( __FILE__ ) ); define( 'TMDO_HIVEPRESS_PATH', plugin_dir_path( __FILE__ ) );
define( 'TMDO_HIVEPRESS_URL', plugin_dir_url( __FILE__ ) ); define( 'TMDO_HIVEPRESS_URL', plugin_dir_url( __FILE__ ) );
define( 'TMDO_HIVEPRESS_FILE', __FILE__ ); define( 'TMDO_HIVEPRESS_FILE', __FILE__ );
+24
View File
@@ -11,6 +11,30 @@ 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
- **`TMDO_HivePress_Attribute_Bridge` 組出正確的 meta key**:HivePress 把每個模型的屬性存成
`hp_{slug}`(例如 `hp_warranty`),不是 `hp_{model}_{slug}`。舊的 key 在資料庫裡不存在,
照它建議的欄位登記後永遠是空的。
---
## [0.5.1] — 2026-10-02 — 重新打包 ## [0.5.1] — 2026-10-02 — 重新打包
### Changed ### Changed
+36
View File
@@ -154,6 +154,8 @@ class TMDO_Listing_Stats {
// Seed the key when it does not exist yet. // Seed the key when it does not exist yet.
if ( ! $updated ) { if ( ! $updated ) {
add_post_meta( $post_id, 'hp_view_count', $new_views, true ); add_post_meta( $post_id, 'hp_view_count', $new_views, true );
} else {
self::resync_view_count( $post_id );
} }
// Remove the warm entry after flush. // Remove the warm entry after flush.
@@ -164,6 +166,40 @@ class TMDO_Listing_Stats {
return $flushed; 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 ───────────────────────────────────── // ── Zone D: Expired Listing Archival ─────────────────────────────────────
/** /**
@@ -20,7 +20,7 @@
* Usage: * Usage:
* $bridge = new TMDO_HivePress_Attribute_Bridge(); * $bridge = new TMDO_HivePress_Attribute_Bridge();
* $candidates = $bridge->discover_attributes(); * $candidates = $bridge->discover_attributes();
* // returns: [['slug' => 'beds', 'type' => 'integer', 'filterable' => true, 'searchable' => true, 'meta_key' => 'hp_listing_beds'], ...] * // returns: [['slug' => 'beds', 'type' => 'integer', 'filterable' => true, 'searchable' => true, 'meta_key' => 'hp_beds'], ...]
* *
* @package WP_Data_Optimizer * @package WP_Data_Optimizer
* @since 3.0.0 * @since 3.0.0
@@ -123,7 +123,8 @@ if ( ! class_exists( 'TMDO_HivePress_Attribute_Bridge' ) ) {
return array( return array(
'model' => $model, 'model' => $model,
'slug' => $slug, 'slug' => $slug,
'meta_key' => sprintf( 'hp_%s_%s', $model, $slug ), // HivePress stores every model's attribute as hp_{slug} (e.g. hp_warranty), not hp_{model}_{slug}.
'meta_key' => 'hp_' . $slug,
'type' => (string) ( $cfg['edit_field']['type'] ?? $cfg['search_field']['type'] ?? 'text' ), 'type' => (string) ( $cfg['edit_field']['type'] ?? $cfg['search_field']['type'] ?? 'text' ),
'filterable' => (bool) ( $cfg['filterable'] ?? false ), 'filterable' => (bool) ( $cfg['filterable'] ?? false ),
'sortable' => (bool) ( $cfg['sortable'] ?? false ), 'sortable' => (bool) ( $cfg['sortable'] ?? false ),
+1 -1
View File
@@ -62,7 +62,7 @@ class HivePressAttributeBridgeTest extends TestCase {
$this->assertSame( 'listing', $record['model'] ); $this->assertSame( 'listing', $record['model'] );
$this->assertSame( 'beds', $record['slug'] ); $this->assertSame( 'beds', $record['slug'] );
$this->assertSame( 'hp_listing_beds', $record['meta_key'] ); $this->assertSame( 'hp_beds', $record['meta_key'] );
$this->assertSame( 'integer', $record['type'] ); $this->assertSame( 'integer', $record['type'] );
$this->assertTrue( $record['filterable'] ); $this->assertTrue( $record['filterable'] );
$this->assertFalse( $record['sortable'] ); $this->assertFalse( $record['sortable'] );