fix(logger): 修正 7 處 Logger::error 參數不足導致的 TypeError(A10)

TMDO_Logger::error() 簽章為 (module, hook, message, context, zone),但 7 處
以 (event, array) 兩參數呼叫 → array 傳給 string $hook 觸發 TypeError:
- 4 個 stress tester 的 batch 失敗路徑
- auto-promoter 的 promote 失敗路徑
- conflict-detector 的欄位衝突路徑
- hook-bus 的 shadow 比對例外路徑(shadow_read 模式必炸)

同時 backport A v3.3.x 的 mass column clear opt-in gate(A9):
delete_all=true 改由 wpdo_allow_mass_column_clear / tmdo_allow_mass_column_clear
filter 明確放行,取代原本「>500 列才擋」的啟發式(小站一次誤呼叫即整欄 NULL)。

註:A 對應處 class-wpdo-hook-bus.php:511,522 傳 3 個 string 給
Logger::warning(string,array),A 側自身有 TypeError,故未照搬其寫法。

unit 379 / integration 398 GREEN

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:12:45 +08:00
parent e8987fafa8
commit f1c577df0d
7 changed files with 35 additions and 26 deletions
@@ -492,7 +492,7 @@ final class TMDO_Comment_Stress_Tester {
$state['completed_at'] = time();
update_option( self::OPT_STATE, $state, false );
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::error( 'comment_stress_test_batch_failed', array( 'message' => $e->getMessage() ) );
TMDO_Logger::error( 'stress_test', 'comment_batch_failed', $e->getMessage() );
}
return;
}
+1 -1
View File
@@ -545,7 +545,7 @@ final class TMDO_Post_Stress_Tester {
$state['completed_at'] = time();
update_option( self::OPT_STATE, $state, false );
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::error( 'post_stress_test_batch_failed', array( 'message' => $e->getMessage() ) );
TMDO_Logger::error( 'stress_test', 'post_batch_failed', $e->getMessage() );
}
return;
}
+1 -1
View File
@@ -515,7 +515,7 @@ final class TMDO_Term_Stress_Tester {
$state['completed_at'] = time();
update_option( self::OPT_STATE, $state, false );
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::error( 'term_stress_test_batch_failed', array( 'message' => $e->getMessage() ) );
TMDO_Logger::error( 'stress_test', 'term_batch_failed', $e->getMessage() );
}
return;
}
+1 -1
View File
@@ -403,7 +403,7 @@ final class TMDO_User_Stress_Tester {
$state['completed_at'] = time();
update_option( self::OPT_STATE, $state, false );
if ( class_exists( 'TMDO_Logger' ) ) {
TMDO_Logger::error( 'stress_test_batch_failed', array( 'message' => $e->getMessage() ) );
TMDO_Logger::error( 'stress_test', 'user_batch_failed', $e->getMessage() );
}
return;
}
+3 -4
View File
@@ -108,11 +108,10 @@ final class TMDO_Auto_Promoter {
$result = TMDO_Mode_Manager::set( $type, TMDO_Mode_Manager::MODE_AEAV_ONLY );
if ( is_wp_error( $result ) ) {
TMDO_Logger::error(
'auto_promoter',
'auto_promote_failed',
array(
'entity_type' => $type,
'error' => $result->get_error_message(),
)
$result->get_error_message(),
array( 'entity_type' => $type )
);
return 'error';
}
@@ -76,11 +76,10 @@ final class TMDO_Conflict_Detector {
if ( $conflicts ) {
TMDO_Logger::error(
'schema_registry',
'field_registration_conflict',
array(
'count' => count( $conflicts ),
'fields' => $conflicts,
)
sprintf( 'Field registration conflict: %d field(s) affected', count( $conflicts ) ),
$conflicts
);
}
+23 -12
View File
@@ -418,12 +418,7 @@ final class TMDO_Hook_Bus {
);
} catch ( \Throwable $e ) {
// 比對失敗不該影響讀取
TMDO_Logger::error(
'shadow_compare_exception',
array(
'error' => $e->getMessage(),
)
);
TMDO_Logger::error( 'entity_bridge', 'shadow_compare_exception', $e->getMessage() );
}
}
@@ -525,23 +520,39 @@ final class TMDO_Hook_Bus {
$default = $field_def['default'] ?? null;
if ( $delete_all ) {
// Safety cap: refuse mass-null if affected row count exceeds threshold.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$row_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
/**
* Opt-in gate for mass column clear (delete_all=true).
*
* Defaults to false — a single mistargetted call NULLs the column for
* every entity, and the previous row-count heuristic let small sites
* (< 500 rows) through unguarded. Enable only in controlled contexts:
* add_filter( 'wpdo_allow_mass_column_clear', '__return_true' );
*
* @param bool $allow Whether to allow the mass clear. Default false.
* @param string $table Flat table name.
* @param string $col Column being cleared.
* @param string $meta_key Original meta key.
*/
$allowed = (bool) apply_filters( 'wpdo_allow_mass_column_clear', false, $table, $col, $meta_key );
/** This filter is documented above (tmdo_* is the forward-looking name). */
$allowed = (bool) apply_filters( 'tmdo_allow_mass_column_clear', $allowed, $table, $col, $meta_key );
if ( $row_count > 500 ) {
if ( ! $allowed ) {
TMDO_Logger::warning(
'intercept_delete_mass_blocked',
array(
'table' => $table,
'col' => $col,
'rows' => $row_count,
'reason' => 'enable via wpdo_allow_mass_column_clear filter',
)
);
return false;
}
TMDO_Logger::info(
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$row_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
TMDO_Logger::warning(
'intercept_delete_all',
array(
'table' => $table,