| 1 |
<?php |
| 2 |
/** |
| 3 |
* MainWP Database Logs Archive. |
| 4 |
* |
| 5 |
* This file handles all interactions with the Client DB. |
| 6 |
* |
| 7 |
* @package MainWP/Dashboard |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace MainWP\Dashboard\Module\Log; |
| 11 |
|
| 12 |
use MainWP\Dashboard\MainWP_DB; |
| 13 |
|
| 14 |
// Exit if accessed directly. |
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* Class Log_DB_Archive |
| 21 |
* |
| 22 |
* @package MainWP\Dashboard |
| 23 |
*/ |
| 24 |
class Log_DB_Archive extends MainWP_DB { |
| 25 |
|
| 26 |
/** |
| 27 |
* Protected static variable to hold the single instance of the class. |
| 28 |
* |
| 29 |
* @var mixed Default null |
| 30 |
*/ |
| 31 |
protected static $instance = null; |
| 32 |
|
| 33 |
/** |
| 34 |
* Return the single instance of the class. |
| 35 |
* |
| 36 |
* @return mixed $instance The single instance of the class. |
| 37 |
*/ |
| 38 |
public static function instance() { |
| 39 |
if ( is_null( static::$instance ) ) { |
| 40 |
static::$instance = new self(); |
| 41 |
} |
| 42 |
return static::$instance; |
| 43 |
} |
| 44 |
|
| 45 |
|
| 46 |
/** |
| 47 |
* Method archive_sites_changes(). |
| 48 |
* |
| 49 |
* @param int $before_timestamp Archive Network Activity data created before time. |
| 50 |
* @param int $by_limit By limit. |
| 51 |
* @param mixed $dismiss Dismiss: false|0|1. |
| 52 |
* |
| 53 |
* @return mixed |
| 54 |
*/ |
| 55 |
public function archive_sites_changes( $before_timestamp = 0, $by_limit = 0, $dismiss = false ) { |
| 56 |
$before_timestamp = 1000000 * $before_timestamp; |
| 57 |
$where = ''; |
| 58 |
$order_by = ''; |
| 59 |
$batch_limit = 0; |
| 60 |
if ( ! empty( $before_timestamp ) ) { |
| 61 |
$where .= ' AND created < ' . (int) $before_timestamp; |
| 62 |
} |
| 63 |
|
| 64 |
if ( ! empty( $by_limit ) ) { |
| 65 |
$order_by .= ' created ASC '; |
| 66 |
$batch_limit = (int) $by_limit; |
| 67 |
} |
| 68 |
|
| 69 |
if ( false !== $dismiss ) { |
| 70 |
$where .= ' AND dismiss = ' . intval( $dismiss ); |
| 71 |
} |
| 72 |
|
| 73 |
$this->bulk_archive_logs( $where, $order_by, $batch_limit ); |
| 74 |
} |
| 75 |
|
| 76 |
|
| 77 |
/** |
| 78 |
* Bulk archive logs + meta with batching (no transaction). |
| 79 |
* |
| 80 |
* @param string $where SQL WHERE condition (MUST be prepared). |
| 81 |
* @param string $order_by Column and direction for ORDER BY (e.g. "created ASC"). |
| 82 |
* @param int $batch_limit Number of rows per batch. |
| 83 |
* |
| 84 |
* @return int Total archived rows. |
| 85 |
*/ |
| 86 |
public function bulk_archive_logs( $where, $order_by, $batch_limit = 1000 ) { |
| 87 |
global $wpdb; |
| 88 |
|
| 89 |
$table_logs = $this->table_name( 'wp_logs' ); |
| 90 |
$table_logs_archive = $this->table_name( 'wp_logs_archive' ); |
| 91 |
$table_meta = $this->table_name( 'wp_logs_meta' ); |
| 92 |
$table_meta_archive = $this->table_name( 'wp_logs_meta_archive' ); |
| 93 |
|
| 94 |
$total_archived = 0; |
| 95 |
|
| 96 |
do { |
| 97 |
// 1. Get batch IDs. |
| 98 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL fragments are built from integer-cast archive inputs; table name is internal. |
| 99 |
$ids = $this->wpdb->get_col( |
| 100 |
"SELECT log_id |
| 101 |
FROM $table_logs |
| 102 |
WHERE 1 $where " . ( ! empty( $order_by ) ? " ORDER BY $order_by " : '' ) . // phpcs:ignore -- NOSONAR -no user input |
| 103 |
( $batch_limit ? ' LIMIT ' . intval( $batch_limit ) : '' ) |
| 104 |
); |
| 105 |
|
| 106 |
if ( empty( $ids ) ) { |
| 107 |
break; |
| 108 |
} |
| 109 |
|
| 110 |
$ids_in = implode( ',', array_map( 'intval', $ids ) ); |
| 111 |
|
| 112 |
// 2. Archive logs (prevent duplicates). |
| 113 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table names are internal. |
| 114 |
$this->wpdb->query( |
| 115 |
"INSERT IGNORE INTO $table_logs_archive |
| 116 |
SELECT l.*, UNIX_TIMESTAMP() AS archived_at |
| 117 |
FROM $table_logs l |
| 118 |
WHERE l.log_id IN ($ids_in)" |
| 119 |
); |
| 120 |
|
| 121 |
// 3. Archive meta (prevent duplicates). |
| 122 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table names are internal. |
| 123 |
$this->wpdb->query( |
| 124 |
"INSERT IGNORE INTO $table_meta_archive |
| 125 |
SELECT m.* |
| 126 |
FROM $table_meta m |
| 127 |
WHERE m.meta_log_id IN ($ids_in)" |
| 128 |
); |
| 129 |
|
| 130 |
// 4. Delete meta first. |
| 131 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table name is internal. |
| 132 |
$this->wpdb->query( |
| 133 |
"DELETE FROM $table_meta |
| 134 |
WHERE meta_log_id IN ($ids_in)" |
| 135 |
); |
| 136 |
|
| 137 |
// 5. Delete logs |
| 138 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $ids_in contains only intval() log IDs selected from the DB; table name is internal. |
| 139 |
$this->wpdb->query( |
| 140 |
"DELETE FROM $table_logs |
| 141 |
WHERE log_id IN ($ids_in)" |
| 142 |
); |
| 143 |
|
| 144 |
$total_archived += count( $ids ); |
| 145 |
|
| 146 |
} while ( count( $ids ) === $batch_limit ); // Continue if we hit the batch limit. |
| 147 |
|
| 148 |
return $total_archived; |
| 149 |
} |
| 150 |
|
| 151 |
|
| 152 |
/** |
| 153 |
* Method archive_log(). |
| 154 |
* |
| 155 |
* @param array $data Log data to archive. |
| 156 |
* |
| 157 |
* @return mixed |
| 158 |
*/ |
| 159 |
public function archive_log( $data ) { |
| 160 |
if ( empty( $data ) || ! is_array( $data ) || empty( $data['log_id'] ) ) { |
| 161 |
return false; |
| 162 |
} |
| 163 |
|
| 164 |
$log_id = $data['log_id']; |
| 165 |
|
| 166 |
$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. |
| 167 |
if ( $log ) { |
| 168 |
$log['archived_at'] = time(); |
| 169 |
$this->wpdb->insert( $this->table_name( 'wp_logs_archive' ), $log ); |
| 170 |
$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. |
| 171 |
|
| 172 |
if ( $log_mt ) { |
| 173 |
foreach ( $log_mt as $mt ) { |
| 174 |
$this->wpdb->insert( $this->table_name( 'wp_logs_meta_archive' ), $mt ); |
| 175 |
} |
| 176 |
} |
| 177 |
|
| 178 |
$this->wpdb->delete( |
| 179 |
$this->table_name( 'wp_logs' ), |
| 180 |
array( |
| 181 |
'log_id' => $log_id, |
| 182 |
) |
| 183 |
); |
| 184 |
|
| 185 |
$this->wpdb->delete( |
| 186 |
$this->table_name( 'wp_logs_meta' ), |
| 187 |
array( |
| 188 |
'meta_log_id' => $log_id, |
| 189 |
) |
| 190 |
); |
| 191 |
|
| 192 |
return $log; |
| 193 |
} |
| 194 |
return false; |
| 195 |
} |
| 196 |
|
| 197 |
/** |
| 198 |
* Method truncate_archive_tables(). |
| 199 |
*/ |
| 200 |
public function truncate_archive_tables() { |
| 201 |
$this->wpdb->query( 'TRUNCATE TABLE ' . $this->table_name( 'wp_logs_archive' ) ); //phpcs:ignore -- ok. |
| 202 |
$this->wpdb->query( 'TRUNCATE TABLE ' . $this->table_name( 'wp_logs_meta_archive' ) ); //phpcs:ignore -- ok. |
| 203 |
delete_transient( 'mainwp_module_log_transient_db_logs_size' ); |
| 204 |
} |
| 205 |
} |
| 206 |
|