fix(2meet-data-optimizer): keep the audit trail of mode changes and snapshot deletions

- TMDO_Logger::notice() is always written to the error log (it had no
  callers); bridge_mode_changed and snapshot_deleted use it. Since PR-095
  info() is silent without WP_DEBUG_LOG, which left no record of who switched
  a mode or deleted a snapshot (security review L1)
- Verify: LoggerTest (notice always logged, info gated), unit 703,
  integration 585, phpcs, stan
- PR: PR-113 (2.3 acceptance security review)
This commit is contained in:
2026-09-27 17:25:56 +08:00
parent 29adb05567
commit e26aadc4ec
4 changed files with 16 additions and 12 deletions
+6 -5
View File
@@ -77,15 +77,16 @@ class TMDO_Logger {
}
/**
* Log a NOTICE-level event — alias of info() for callers that want a stronger
* level intent. Same routing (error_log only).
* Log a NOTICE-level event: always written to the PHP error log (unlike
* info()), for the audit trail of operator actions such as mode changes
* and snapshot deletions.
*
* @param string $event Event name.
* @param array $context Structured context.
* @param string $event Event name.
* @param array<string, mixed> $context Structured context.
* @return void
*/
public static function notice( string $event, array $context = array() ): void {
self::info( $event, $context );
error_log( sprintf( '[WPDO][NOTICE][%s] %s', $event, $context ? wp_json_encode( $context, JSON_UNESCAPED_UNICODE ) : '' ) );
}
/**
+1 -1
View File
@@ -159,7 +159,7 @@ final class TMDO_Mode_Manager {
$entered[ $entity_type ] = time();
update_option( self::OPT_ENTERED_AT, $entered, false );
TMDO_Logger::info(
TMDO_Logger::notice(
'bridge_mode_changed',
array(
'entity_type' => $entity_type,
@@ -310,7 +310,7 @@ class TMDO_Snapshot_Manager {
}
$table = $wpdb->prefix . self::TABLE_SLUG;
$deleted = $wpdb->delete( $table, array( 'snapshot_id' => $snapshot_id ), array( '%s' ) ); // phpcs:ignore WordPress.DB
TMDO_Logger::info( 'snapshot_deleted', array( 'snapshot_id' => $snapshot_id ) );
TMDO_Logger::notice( 'snapshot_deleted', array( 'snapshot_id' => $snapshot_id ) );
return false !== $deleted;
}
+8 -5
View File
@@ -145,13 +145,10 @@ class LoggerTest extends TestCase {
return $out;
}
public function test_info_and_notice_stay_out_of_the_log_unless_debug_logging_is_on(): void {
public function test_info_stays_out_of_the_log_unless_debug_logging_is_on(): void {
$GLOBALS['_wp_filter_callbacks']['tmdo_log_info'] = array();
$this->assertSame( '', $this->logged( static function () {
WPDO_Logger::info( 'cache_flushed' );
WPDO_Logger::notice( 'mode_changed' );
} ) );
$this->assertSame( '', $this->logged( static fn() => WPDO_Logger::info( 'cache_flushed' ) ) );
add_filter( 'tmdo_log_info', static fn() => true );
try {
@@ -161,6 +158,12 @@ class LoggerTest extends TestCase {
}
}
public function test_notices_are_always_logged_for_the_audit_trail(): void {
$GLOBALS['_wp_filter_callbacks']['tmdo_log_info'] = array();
$this->assertStringContainsString( '[WPDO][NOTICE][bridge_mode_changed]', $this->logged( static fn() => WPDO_Logger::notice( 'bridge_mode_changed', array( 'to' => 'dual_write' ) ) ) );
}
public function test_warnings_are_always_logged(): void {
$this->assertStringContainsString( '[WPDO][WARN][odd_return]', $this->logged( static fn() => WPDO_Logger::warning( 'odd_return' ) ) );
}