prefix . self::TABLE; $charset = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, user_id bigint(20) unsigned NOT NULL DEFAULT 0, tool_name varchar(191) NOT NULL, args_hash varchar(64) NOT NULL DEFAULT '', status varchar(32) NOT NULL DEFAULT 'success', message text, duration_ms int unsigned NOT NULL DEFAULT 0, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY tool_name (tool_name), KEY created_at (created_at) ) {$charset};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } /** * Log a tool execution. * * @param string $tool_name Tool name. * @param array $args Tool arguments. * @param string $status success|error|blocked. * @param string $message Optional message. * @param int $duration Duration in ms. */ public static function log( $tool_name, $args, $status, $message = '', $duration = 0 ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Writing to this plugin's own custom audit table; no core API exists and log rows must not be cached. $wpdb->insert( $wpdb->prefix . self::TABLE, array( 'user_id' => get_current_user_id(), 'tool_name' => sanitize_text_field( $tool_name ), 'args_hash' => hash( 'sha256', wp_json_encode( $args ) ), 'status' => sanitize_text_field( $status ), 'message' => sanitize_textarea_field( $message ), 'duration_ms' => absint( $duration ), 'created_at' => current_time( 'mysql', true ), ), array( '%d', '%s', '%s', '%s', '%s', '%d', '%s' ) ); } /** * Get recent audit entries. * * @param int $limit Max rows. * @return array */ public static function get_recent( $limit = 20 ) { global $wpdb; $table = $wpdb->prefix . self::TABLE; $limit = max( 1, min( 100, absint( $limit ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reading this plugin's own custom audit table; intentionally uncached so the log is always current. $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT id, user_id, tool_name, status, message, duration_ms, created_at FROM %i ORDER BY id DESC LIMIT %d', $table, $limit ), ARRAY_A ); return is_array( $rows ) ? $rows : array(); } }