From a0847a27bf9da757fb03615ee53678a3ca6602b5 Mon Sep 17 00:00:00 2001 From: wpdev Date: Fri, 31 Jul 2026 05:16:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(audit,sso):=20audit=20=E8=A1=A8=E8=A3=9C=20?= =?UTF-8?q?group=5Fname/action=20=E6=AC=84=20+=20=E8=A8=BB=E5=86=8A=20Audi?= =?UTF-8?q?t=5FLogger=20+=20known=5Flogin=5Fips=EF=BC=88A11/A12/A14?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wpdo_audit CREATE 補 group_name varchar(50) 與 action varchar(20): TMDO_Audit_Logger::write_row() 一直在寫這兩個 key,表卻沒有該欄位。 SCHEMA_VERSION 2.0.0 → 2.1.0 觸發既有站台 dbDelta ALTER;主檔與兩個 test bootstrap 的 TMDO_DB_VERSION 同步(BackCompatTest 守門要求一致)。 - TMDO_Core::run() 註冊 TMDO_Audit_Logger::init():先前完全沒掛, wpdo_after_write / wpdo_after_delete 的稽核監聽器形同不存在。 - register_sso_group() 補 known_login_ips(A v3.4.4):2meet-spoke-sso 的 TMSO_Login_Notify 以 set_entity 寫此欄,未註冊時降級落 wp_usermeta。 對應測試改為 test_sso_group_has_eight_fields。 unit 379 / integration 398 GREEN Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY --- 2meet-data-optimizer.php | 2 +- includes/class-tmdo-core.php | 5 +++++ includes/class-tmdo-installer.php | 4 +++- includes/integrations/class-tmdo-member-fields.php | 5 +++++ tests/bootstrap.php | 2 +- tests/integration/bootstrap.php | 2 +- tests/unit/MemberFields/MemberFieldsRegistrationTest.php | 6 ++++-- 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/2meet-data-optimizer.php b/2meet-data-optimizer.php index 69248a4..07aca6d 100644 --- a/2meet-data-optimizer.php +++ b/2meet-data-optimizer.php @@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) { // ── Constants ────────────────────────────────────────────────────────────── define( 'TMDO_VERSION', '0.1.0' ); -define( 'TMDO_DB_VERSION', '2.0.0' ); +define( 'TMDO_DB_VERSION', '2.1.0' ); define( 'TMDO_PATH', plugin_dir_path( __FILE__ ) ); define( 'TMDO_URL', plugin_dir_url( __FILE__ ) ); define( 'TMDO_FILE', __FILE__ ); diff --git a/includes/class-tmdo-core.php b/includes/class-tmdo-core.php index bb2497a..58906b9 100644 --- a/includes/class-tmdo-core.php +++ b/includes/class-tmdo-core.php @@ -134,6 +134,11 @@ class TMDO_Core { // v2.0.0: boot the unified Hook Bus when explicitly enabled. TMDO_Hook_Bus_Bridge::maybe_init_hook_bus(); + // Register audit log listeners for wpdo_after_write / wpdo_after_delete. + if ( class_exists( 'TMDO_Audit_Logger' ) ) { + TMDO_Audit_Logger::init(); + } + // v2.0.0: fire the Custom Table Registry hook so partner plugins can register. TMDO_Custom_Table_Registry::instance()->fire_registration(); diff --git a/includes/class-tmdo-installer.php b/includes/class-tmdo-installer.php index a030b53..fd434f4 100644 --- a/includes/class-tmdo-installer.php +++ b/includes/class-tmdo-installer.php @@ -36,7 +36,7 @@ class TMDO_Installer { * wpdo_audit / wpdo_shadow_diffs / wpdo_site_metrics / wpdo_uni_options * tables installed via install_v2_tables() during V2 upgrade. */ - private const SCHEMA_VERSION = '2.0.0'; + private const SCHEMA_VERSION = '2.1.0'; // ── Public API ──────────────────────────────────────────────────────── @@ -289,7 +289,9 @@ class TMDO_Installer { user_id bigint(20) unsigned NOT NULL DEFAULT 0, entity_type varchar(20) NOT NULL DEFAULT '', entity_id bigint(20) unsigned NOT NULL DEFAULT 0, + group_name varchar(50) NOT NULL DEFAULT '', meta_key varchar(255) NOT NULL DEFAULT '', + action varchar(20) NOT NULL DEFAULT '', op varchar(20) NOT NULL DEFAULT '', value_before longtext, value_after longtext, diff --git a/includes/integrations/class-tmdo-member-fields.php b/includes/integrations/class-tmdo-member-fields.php index 47cc468..e4ed84a 100644 --- a/includes/integrations/class-tmdo-member-fields.php +++ b/includes/integrations/class-tmdo-member-fields.php @@ -266,6 +266,11 @@ final class TMDO_Member_Fields { 'default' => 0, 'label' => 'SSO login count', ), + array( + 'key' => 'known_login_ips', + 'type' => 'textarea', + 'label' => 'Recent login IPs JSON array (new-device notification) — replaces _tmso_known_ips usermeta', + ), ) ); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 25dbbeb..b4b47db 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -18,7 +18,7 @@ define( 'TMDO_PATH', dirname( __DIR__ ) . '/' ); define( 'TMDO_URL', 'http://localhost/wp-content/plugins/2meet-data-optimizer/' ); define( 'TMDO_FILE', dirname( __DIR__ ) . '/2meet-data-optimizer.php' ); define( 'TMDO_VERSION', '0.1.0' ); -define( 'TMDO_DB_VERSION', '2.0.0' ); +define( 'TMDO_DB_VERSION', '2.1.0' ); define( 'TMDO_IS_SQLITE', false ); define( 'TMDO_IS_MYSQL', true ); diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php index 3745bcf..6828c3b 100644 --- a/tests/integration/bootstrap.php +++ b/tests/integration/bootstrap.php @@ -32,7 +32,7 @@ if ( false === $_plugin_header || ! preg_match( '/Version:\s*([0-9A-Za-z.\-+]+)/ define( 'TMDO_VERSION', $_ver[1] ); unset( $_plugin_header, $_ver ); -define( 'TMDO_DB_VERSION', '2.0.0' ); +define( 'TMDO_DB_VERSION', '2.1.0' ); define( 'TMDO_IS_SQLITE', false ); define( 'TMDO_IS_MYSQL', true ); if ( ! defined( 'TMDO_TABLE_PREFIX' ) ) { define( 'TMDO_TABLE_PREFIX', 'wpdo_' ); } diff --git a/tests/unit/MemberFields/MemberFieldsRegistrationTest.php b/tests/unit/MemberFields/MemberFieldsRegistrationTest.php index 5079b2d..d80d1ae 100644 --- a/tests/unit/MemberFields/MemberFieldsRegistrationTest.php +++ b/tests/unit/MemberFields/MemberFieldsRegistrationTest.php @@ -144,10 +144,12 @@ class MemberFieldsRegistrationTest extends TestCase { // ── sso group ──────────────────────────────────────────────────────────── - public function test_sso_group_has_seven_fields(): void { + public function test_sso_group_has_eight_fields(): void { WPDO_Member_Fields::register_entity_fields(); $fields = WPDO_Entity_Registry::get_group_fields( 'user', 'sso' ); - $this->assertCount( 7, $fields ); + // v3.4.4: known_login_ips registered (was leaking to wp_usermeta via set_entity). + $this->assertCount( 8, $fields ); + $this->assertNotNull( $this->find_field( $fields, 'known_login_ips' ) ); } public function test_hub_global_user_id_is_searchable(): void {