From 6f117f4e9b343c3af08251ee572ee146df44d264 Mon Sep 17 00:00:00 2001 From: wpdev Date: Mon, 28 Sep 2026 18:43:50 +0800 Subject: [PATCH] fix(2meet-data-optimizer): 1.4.2 acceptance fixes for archive sweep and uninstall - Archive sweep waits until idx_original_meta exists (a plugin update does not run dbDelta; without the index NOT EXISTS scans the archive per row) - Duplicate archive cleanup runs from WP-CLI only, never in a web activation - Uninstall keeps the LatePoint add-on's tmdo_latepoint_db_version - AJAX save errors stay on screen instead of fading after 2.5 s - Verify: 2 new integration tests; unit 711, integration 612, phpcs 0, stan OK - PR: PR-141 (2.4.2 correctness review) --- CHANGELOG.md | 12 ++++++++++-- admin/assets/wpdo-settings.js | 5 ++++- includes/class-tmdo-installer.php | 6 ++++-- includes/zones/class-tmdo-zone-archive.php | 4 ++++ readme.txt | 2 +- tests/integration/InstallerCleanupTest.php | 3 ++- .../integration/ZoneArchiveIntegrationTest.php | 17 ++++++++++++++++- 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f13d8..22ae452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,8 +32,16 @@ Versioning follows [Semantic Versioning](https://semver.org/). ### 升級後 - 每個站台執行一次 `wp tmdo install`:為 archive 表加上 `idx_original_meta` 並刪除重複的 archive - 列。表很大時請在離峰時段執行(CLI,不在 web 請求中做 DDL)。未執行也不影響功能,只是 sweep - 的 NOT EXISTS 檢查少了索引。 + 列(刪除重複只在 CLI 執行,web 啟用外掛時不做)。表很大時請在離峰時段執行。 +- **未執行前 archive sweep 會暫停**(沒有索引時 NOT EXISTS 會對每筆候選掃整張 archive 表)。 + sweep 只複製、不刪 postmeta,暫停不會遺失資料。 +- 解除安裝的 option 清理保留 LatePoint AddOn 的 `tmdo_latepoint_db_version`;設定頁的 AJAX 錯誤訊息 + 不再 2.5 秒後自動消失。(PR-141,驗收修正) + +### 已知、未修改 + +- 已歸檔的 meta 以 `meta_id` 判斷:文章還原、修改後再次丟到回收桶,archive 保留的是第一次的值。 +- post 壓測工具預設仍是 `product`,但 product 已沒有 flat 表,量測會回報 `flat_table_missing`。 --- diff --git a/admin/assets/wpdo-settings.js b/admin/assets/wpdo-settings.js index 6389e71..bab4d08 100644 --- a/admin/assets/wpdo-settings.js +++ b/admin/assets/wpdo-settings.js @@ -45,7 +45,7 @@ } /** - * Show a transient status message, then fade it out after 2.5s. + * Show a status message; success fades out after 2.5s, errors stay. * * @param {HTMLElement} statusEl * @param {string} message @@ -56,6 +56,9 @@ statusEl.style.color = isError ? 'var(--color-error, #b03d3d)' : 'var(--color-success, #5a8c5a)'; statusEl.classList.add( 'visible' ); clearTimeout( statusEl._wpdo_timer ); + if ( isError ) { + return; + } statusEl._wpdo_timer = setTimeout( () => { statusEl.classList.remove( 'visible' ); }, 2500 ); diff --git a/includes/class-tmdo-installer.php b/includes/class-tmdo-installer.php index 2b7df7d..c5a01bd 100644 --- a/includes/class-tmdo-installer.php +++ b/includes/class-tmdo-installer.php @@ -218,7 +218,8 @@ class TMDO_Installer { self::install_v2_tables(); // 1.4.2: archive sweeps before this release copied trashed meta daily. - if ( class_exists( 'TMDO_Zone_Archive' ) ) { + // One unbounded DELETE: CLI only, never in a web activation. + if ( defined( 'WP_CLI' ) && WP_CLI && class_exists( 'TMDO_Zone_Archive' ) ) { global $wpdb; $archive = TMDO_Zone_Archive::table(); if ( $archive === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $archive ) ) ) ) { @@ -993,6 +994,7 @@ class TMDO_Installer { // accidental option removal. // 1.4.2: tmdo_* options and their transients too (tmdo_schema_deferred, // tmdo_entity_indexes, tmdo_cov_* …) — a reinstall saw stale ones. + // tmdo_latepoint_* belongs to the LatePoint add-on. foreach ( array( 'wpdo_', 'tmdo_', '_transient_tmdo_', '_transient_timeout_tmdo_' ) as $prefix ) { $residual = $wpdb->get_col( $wpdb->prepare( @@ -1001,7 +1003,7 @@ class TMDO_Installer { ) ); foreach ( (array) $residual as $opt ) { - if ( preg_match( '/^(_transient_(timeout_)?)?(wpdo|tmdo)_[a-zA-Z0-9_]+$/', $opt ) && delete_option( $opt ) ) { + if ( preg_match( '/^(_transient_(timeout_)?)?(wpdo|tmdo)_(?!latepoint_)[a-zA-Z0-9_]+$/', $opt ) && delete_option( $opt ) ) { ++$options_deleted; } } diff --git a/includes/zones/class-tmdo-zone-archive.php b/includes/zones/class-tmdo-zone-archive.php index bed8eb3..21a67f0 100644 --- a/includes/zones/class-tmdo-zone-archive.php +++ b/includes/zones/class-tmdo-zone-archive.php @@ -213,6 +213,10 @@ class TMDO_Zone_Archive { // Rows already archived are skipped: the sweep used to copy the same // trashed meta again every day (2.4.2 PR-136). $table = self::table(); + // Without the index each candidate scans the archive: wait for `wp tmdo install`. + if ( ! TMDO_IS_SQLITE && ! $wpdb->get_var( "SHOW INDEX FROM `{$table}` WHERE Key_name = 'idx_original_meta'" ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- internal table name + return 0; + } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal self::table() $rows = $wpdb->get_results( diff --git a/readme.txt b/readme.txt index b14e9d2..2086be0 100644 --- a/readme.txt +++ b/readme.txt @@ -109,7 +109,7 @@ The Classifier tab analyzes your existing `wp_postmeta` data (access frequency, = 1.4.2 = * The settings page shows why a bridge-mode change was refused (unsafe jump, Promotion Guard) instead of reporting it saved -* Archive sweep skips meta already archived; `wp tmdo install` adds an index on original_meta_id and removes duplicate archive rows +* Archive sweep skips meta already archived; `wp tmdo install` adds an index on original_meta_id and removes duplicate archive rows (the sweep waits until the index exists) * Entity health sees a running backfill (batches are scheduled with entity and group args) * Deactivate and uninstall clear every scheduled event, including ones scheduled with args; uninstall also removes tmdo_* options and transients * The refused-uninstall notice points to `wp tmdo bridge-restore-native`; the post stress tester no longer uses the retired wc_product table diff --git a/tests/integration/InstallerCleanupTest.php b/tests/integration/InstallerCleanupTest.php index a057cd8..be73e63 100644 --- a/tests/integration/InstallerCleanupTest.php +++ b/tests/integration/InstallerCleanupTest.php @@ -98,7 +98,7 @@ class InstallerCleanupTest extends TestCase { /** tmdo_* options and transients were left behind; a reinstall then saw stale deferrals and indexes (F-5). */ public function test_uninstall_removes_tmdo_options_and_transients(): void { global $wpdb; - foreach ( array( 'tmdo_schema_deferred', 'tmdo_entity_indexes', '_transient_tmdo_cov_x', '_transient_timeout_tmdo_cov_x', 'hivepress_settings' ) as $name ) { + foreach ( array( 'tmdo_schema_deferred', 'tmdo_entity_indexes', '_transient_tmdo_cov_x', '_transient_timeout_tmdo_cov_x', 'hivepress_settings', 'tmdo_latepoint_db_version' ) as $name ) { $wpdb->insert( $wpdb->options, array( 'option_name' => $name, 'option_value' => '1' ) ); $GLOBALS['_wp_options'][ $name ] = '1'; } @@ -109,6 +109,7 @@ class InstallerCleanupTest extends TestCase { $this->assertArrayNotHasKey( $name, $GLOBALS['_wp_options'], $name ); } $this->assertArrayHasKey( 'hivepress_settings', $GLOBALS['_wp_options'], 'other plugins untouched' ); + $this->assertArrayHasKey( 'tmdo_latepoint_db_version', $GLOBALS['_wp_options'], 'the LatePoint add-on owns it' ); } public function test_drops_static_tables(): void { diff --git a/tests/integration/ZoneArchiveIntegrationTest.php b/tests/integration/ZoneArchiveIntegrationTest.php index 462da4b..b932eb9 100644 --- a/tests/integration/ZoneArchiveIntegrationTest.php +++ b/tests/integration/ZoneArchiveIntegrationTest.php @@ -29,7 +29,8 @@ class ZoneArchiveIntegrationTest extends TestCase { `original_meta_id` bigint(20) unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `post_id` (`post_id`), - KEY `archived_at` (`archived_at`) + KEY `archived_at` (`archived_at`), + KEY `idx_original_meta` (`original_meta_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); } @@ -81,6 +82,20 @@ class ZoneArchiveIntegrationTest extends TestCase { } } + /** Until `wp tmdo install` adds the index, NOT EXISTS would scan the archive per candidate. */ + public function test_sweep_waits_for_the_original_meta_index(): void { + global $wpdb; + $this->seed_trashed_post(); + $wpdb->query( 'ALTER TABLE `' . self::TABLE . '` DROP INDEX `idx_original_meta`' ); + try { + $this->assertSame( 0, WPDO_Zone_Archive::sweep( 90, false ) ); + $this->assertSame( 0, $this->archive_rows() ); + } finally { + $wpdb->query( 'ALTER TABLE `' . self::TABLE . '` ADD KEY `idx_original_meta` (`original_meta_id`)' ); + $this->drop_posts(); + } + } + public function test_remove_duplicates_keeps_the_first_copy_of_each_meta_row(): void { WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_price', '5', 101 ); WPDO_Zone_Archive::archive( 10, 'hp_listing', 'hp_price', '5', 101 );