prefix . 'eistat_' . $table; } private static function is_valid_table($table) { return in_array($table, self::tables(), true); } public static function export_table_to_csv($table, $from, $to) { global $wpdb; if (!self::is_valid_table($table)) return false; $full = self::full_table($table); $tmp = wp_tempnam('eistat_' . $table . '_'); if (!$tmp) return false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen $fp = fopen($tmp, 'w'); if (!$fp) return false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite($fp, "\xEF\xBB\xBF"); $sql_cols = "SHOW COLUMNS FROM " . $full; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $columns = $wpdb->get_col($sql_cols, 0); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fputcsv($fp, $columns, ','); $offset = 0; $limit = 5000; while (true) { $sql = "SELECT * FROM " . $full . " WHERE date >= %s AND date <= %s ORDER BY date ASC LIMIT %d OFFSET %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $rows = $wpdb->get_results($wpdb->prepare($sql, $from, $to, $limit, $offset), ARRAY_A); if (!$rows) break; foreach ($rows as $row) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fputcsv($fp, $row, ','); } $offset += $limit; if (count($rows) < $limit) break; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose fclose($fp); return $tmp; } public static function download_csv($table, $from, $to) { $tmp = self::export_table_to_csv($table, $from, $to); if (!$tmp) { wp_die(esc_html__('Failed to generate CSV.', 'statinform')); } $filename = 'eistat_' . $table . '_' . $from . '_' . $to . '.csv'; header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . filesize($tmp)); header('Cache-Control: no-cache, no-store, must-revalidate'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile readfile($tmp); wp_delete_file($tmp); exit; } public static function create_archive($tables, $from, $to) { if (!class_exists('ZipArchive')) return false; $dir = self::archive_dir(); if (!file_exists($dir)) wp_mkdir_p($dir); $filename = 'eistat_archive_' . $from . '_' . $to . '_' . wp_date('His') . '.zip'; $path = $dir . $filename; $zip = new ZipArchive(); if ($zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { return false; } $manifest = [ 'plugin' => 'StatInform', 'version' => EISTAT_VERSION, 'created_at' => current_time('mysql'), 'from' => $from, 'to' => $to, 'tables' => [], ]; $tmp_files = []; foreach ($tables as $table) { if (!self::is_valid_table($table)) continue; $tmp = self::export_table_to_csv($table, $from, $to); if (!$tmp) continue; $name = $table . '_' . $from . '_' . $to . '.csv'; $zip->addFile($tmp, $name); $tmp_files[] = $tmp; $manifest['tables'][$table] = [ 'file' => $name, 'size' => filesize($tmp), ]; } $zip->addFromString( 'manifest.json', wp_json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) ); $zip->close(); foreach ($tmp_files as $tmp) { wp_delete_file($tmp); } return $filename; } public static function list_archives() { $dir = self::archive_dir(); $files = glob($dir . '*.zip'); if (!$files) return []; $result = []; foreach ($files as $f) { $result[] = [ 'name' => basename($f), 'size' => filesize($f), 'date' => filemtime($f), ]; } usort($result, function ($a, $b) { return $b['date'] - $a['date']; }); return $result; } public static function download_archive($name) { $name = basename($name); if (!preg_match('/^eistat_archive_[\d\-_]+\.zip$/', $name)) { wp_die(esc_html__('Invalid filename.', 'statinform')); } $path = self::archive_dir() . $name; if (!file_exists($path)) { wp_die(esc_html__('Archive not found.', 'statinform')); } header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . $name . '"'); header('Content-Length: ' . filesize($path)); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile readfile($path); exit; } public static function delete_archive($name) { $name = basename($name); if (!preg_match('/^eistat_archive_[\d\-_]+\.zip$/', $name)) return false; $path = self::archive_dir() . $name; if (!file_exists($path)) return false; return wp_delete_file($path); } public static function delete_before($date) { global $wpdb; $result = []; foreach (self::tables() as $table) { $full = self::full_table($table); $sql = "DELETE FROM " . $full . " WHERE date < %s"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $deleted = $wpdb->query($wpdb->prepare($sql, $date)); $result[$table] = (int)$deleted; } delete_transient('eistat_db_size_info'); return $result; } public static function db_size_info() { global $wpdb; $total_mb = 0; $total_rows = 0; foreach (self::tables() as $t) { $full = self::full_table($t); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $row = $wpdb->get_row($wpdb->prepare("SHOW TABLE STATUS LIKE %s", $full)); if ($row) { $total_mb += ($row->Data_length + $row->Index_length) / 1024 / 1024; $total_rows += (int)$row->Rows; } } return ['mb' => round($total_mb, 1), 'rows' => $total_rows]; } }