bulk_archive_logs( $where, $order_by, $batch_limit ); } /** * Bulk archive logs + meta with batching (no transaction). * * @param string $where SQL WHERE condition (MUST be prepared). * @param string $order_by Column and direction for ORDER BY (e.g. "created ASC"). * @param int $batch_limit Number of rows per batch. * * @return int Total archived rows. */ public function bulk_archive_logs( $where, $order_by, $batch_limit = 1000 ) { global $wpdb; $table_logs = $this->table_name( 'wp_logs' ); $table_logs_archive = $this->table_name( 'wp_logs_archive' ); $table_meta = $this->table_name( 'wp_logs_meta' ); $table_meta_archive = $this->table_name( 'wp_logs_meta_archive' ); $total_archived = 0; do { // 1. Get batch IDs. // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL fragments are built from integer-cast archive inputs; table name is internal. $ids = $this->wpdb->get_col( "SELECT log_id FROM $table_logs WHERE 1 $where " . ( ! empty( $order_by ) ? " ORDER BY $order_by " : '' ) . // phpcs:ignore -- NOSONAR -no user input ( $batch_limit ? ' LIMIT ' . intval( $batch_limit ) : '' ) ); if ( empty( $ids ) ) { break; } $ids_in = implode( ',', array_map( 'intval', $ids ) ); // 2. Archive logs (prevent duplicates). // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table names are internal. $this->wpdb->query( "INSERT IGNORE INTO $table_logs_archive SELECT l.*, UNIX_TIMESTAMP() AS archived_at FROM $table_logs l WHERE l.log_id IN ($ids_in)" ); // 3. Archive meta (prevent duplicates). // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table names are internal. $this->wpdb->query( "INSERT IGNORE INTO $table_meta_archive SELECT m.* FROM $table_meta m WHERE m.meta_log_id IN ($ids_in)" ); // 4. Delete meta first. // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table name is internal. $this->wpdb->query( "DELETE FROM $table_meta WHERE meta_log_id IN ($ids_in)" ); // 5. Delete logs // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table name is internal. $this->wpdb->query( "DELETE FROM $table_logs WHERE log_id IN ($ids_in)" ); $total_archived += count( $ids ); } while ( count( $ids ) === $batch_limit ); // Continue if we hit the batch limit. return $total_archived; } /** * Method archive_log(). * * @param array $data Log data to archive. * * @return mixed */ public function archive_log( $data ) { if ( empty( $data ) || ! is_array( $data ) || empty( $data['log_id'] ) ) { return false; } $log_id = $data['log_id']; $log = $this->wpdb->get_row( $this->wpdb->prepare( 'SELECT * FROM ' . $this->table_name('wp_logs') . ' WHERE log_id = %d', $log_id ), ARRAY_A ); //phpcs:ignore -- NOSONAR -ok. if ( $log ) { $log['archived_at'] = time(); $this->wpdb->insert( $this->table_name( 'wp_logs_archive' ), $log ); $log_mt = $this->wpdb->get_results( $this->wpdb->prepare( 'SELECT * FROM ' . $this->table_name('wp_logs_meta') . ' WHERE meta_log_id = %d', $log_id ), ARRAY_A ); //phpcs:ignore -- NOSONAR -ok. if ( $log_mt ) { foreach ( $log_mt as $mt ) { $this->wpdb->insert( $this->table_name( 'wp_logs_meta_archive' ), $mt ); } } $this->wpdb->delete( $this->table_name( 'wp_logs' ), array( 'log_id' => $log_id, ) ); $this->wpdb->delete( $this->table_name( 'wp_logs_meta' ), array( 'meta_log_id' => $log_id, ) ); return $log; } return false; } /** * Method truncate_archive_tables(). */ public function truncate_archive_tables() { $this->wpdb->query( 'TRUNCATE TABLE ' . $this->table_name( 'wp_logs_archive' ) ); //phpcs:ignore -- ok. $this->wpdb->query( 'TRUNCATE TABLE ' . $this->table_name( 'wp_logs_meta_archive' ) ); //phpcs:ignore -- ok. delete_transient( 'mainwp_module_log_transient_db_logs_size' ); } }