get_col('SHOW TABLES'); $chunk_size = 300; // Smaller chunks for safety foreach ($tables as $table) { $create = $wpdb->get_row("SHOW CREATE TABLE `$table`", ARRAY_N); fwrite($handle, $create[1] . ";\n\n"); $offset = 0; while (true) { $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM `$table` LIMIT %d OFFSET %d", $chunk_size, $offset), ARRAY_A); if (empty($rows)) break; foreach ($rows as $row) { $values = array_map([$wpdb, '_real_escape'], array_values($row)); $insert = "INSERT INTO `$table` VALUES ('" . implode("','", $values) . "');\n"; fwrite($handle, $insert); } $offset += $chunk_size; unset($rows); gc_collect_cycles(); $mem = memory_get_usage(true) / 1024 / 1024; if ($mem > 110) { fclose($handle); @unlink($file); dapper_debug_log('Memory high during DB export — aborted'); return false; } } fwrite($handle, "\n\n"); } fclose($handle); dapper_debug_log('DB backup written to: ' . basename($file)); return $file; } /* * Memory-safe zip to dated folder (optional folders) */ function dapper_zip_essential_files($backup_folder) { if (!dapper_ensure_backup_dir()) return false; $zip_files = []; // Collect successful zips $backup_folders = []; if (get_option('dapper_backup_themes', 'on') === 'on') { $backup_folders['themes'] = WP_CONTENT_DIR . '/themes'; } if (get_option('dapper_backup_plugins', 'on') === 'on') { $backup_folders['plugins'] = WP_CONTENT_DIR . '/plugins'; } if (get_option('dapper_backup_include_media', 'off') === 'on') { $backup_folders['uploads'] = WP_CONTENT_DIR . '/uploads'; } if (empty($backup_folders)) { dapper_debug_log('No folders selected for file backup'); return $zip_files; } foreach ($backup_folders as $type => $root) { if (!is_dir($root)) { dapper_debug_log('Folder not found, skipping: ' . $type); continue; } $zip_file = $backup_folder . '/' . $type . '-' . date('Y-m-d_H-i-s') . '.zip'; $zip = new ZipArchive(); if ($zip->open($zip_file, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { dapper_debug_log('Failed to open ' . $type . ' zip: ' . $zip_file); continue; } $file_count = 0; $chunk_count = 0; $max_per_chunk = 10000; // Reopen every 10k files to avoid timeout $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { if ($file->isDir()) continue; $file_path = $file->getRealPath(); $relative = substr($file_path, strlen(WP_CONTENT_DIR) + 1); $zip->addFile($file_path, $relative); $file_count++; $chunk_count++; if ($chunk_count % 500 === 0) { $mem_mb = memory_get_usage(true) / 1024 / 1024; dapper_debug_log("Added $file_count files to $type zip — memory: $mem_mb MB"); } // Reopen zip every max_per_chunk files to reset buffers and avoid timeout if ($chunk_count >= $max_per_chunk) { $zip->close(); dapper_debug_log("Chunk complete for $type ($file_count total) — reopening zip"); $zip = new ZipArchive(); if ($zip->open($zip_file, ZipArchive::CREATE) !== true) { // CREATE appends, doesn't overwrite dapper_debug_log('Failed to reopen ' . $type . ' zip'); break; } $chunk_count = 0; // Reset chunk counter } } $zip->close(); if (!file_exists($zip_file) || filesize($zip_file) < 1024) { dapper_debug_log($type . ' zip empty or failed: ' . $zip_file); @unlink($zip_file); continue; } dapper_debug_log($type . ' zip completed with ' . $file_count . ' files: ' . basename($zip_file)); $zip_files[] = $zip_file; } return $zip_files; } /** * Create full dated backup set */ function dapper_create_new_backup() { $backup_folder = dapper_create_backup_folder(); if (!$backup_folder) return false; $db_file = dapper_export_database($backup_folder); $files_zip = dapper_zip_essential_files($backup_folder); if (!$db_file && !$files_zip) { // Clean up empty folder if both failed @rmdir($backup_folder); return false; } dapper_cleanup_old_backups(); return $backup_folder; } /** * Cleanup: keep only last N dated folders */ function dapper_cleanup_old_backups($keep = DAPPER_BACKUP_RETENTION) { $folders = glob(DAPPER_BACKUP_BASE_DIR . '/*', GLOB_ONLYDIR); if (count($folders) <= $keep) return; usort($folders, function($a, $b) { return filemtime($b) <=> filemtime($a); }); for ($i = $keep; $i < count($folders); $i++) { // Delete folder recursively array_map('unlink', glob("$folders[$i]/*")); @rmdir($folders[$i]); dapper_debug_log('Deleted old backup folder: ' . basename($folders[$i])); } } /** * Get list of dated backups for display */ function dapper_get_old_backups() { $folders = glob(DAPPER_BACKUP_BASE_DIR . '/*', GLOB_ONLYDIR); $backups = []; foreach ($folders as $folder) { $timestamp = basename($folder); $ts = strtotime(str_replace('_', ' ', $timestamp)); $db_file = glob($folder . '/database-*.sql'); $zip_file = glob($folder . '/files-*.zip'); $backups[] = [ 'folder' => $folder, 'timestamp' => $ts, 'date_fmt' => date('Y-m-d H:i:s', $ts), 'db' => !empty($db_file) ? $db_file[0] : '', 'files' => !empty($zip_file) ? $zip_file[0] : '', 'size_db' => !empty($db_file) ? filesize($db_file[0]) : 0, 'size_files'=> !empty($zip_file) ? filesize($zip_file[0]) : 0, ]; } // Sort newest first usort($backups, function($a, $b) { return $b['timestamp'] <=> $a['timestamp']; }); return $backups; } /** * Display backups in settings (dated folders) */ function display_old_backup_list() { $folders = glob(DAPPER_BACKUP_BASE_DIR . '/*', GLOB_ONLYDIR); if (empty($folders)) { echo '

No backups yet.

'; return; } usort($folders, function($a, $b) { return filemtime($b) <=> filemtime($a); }); echo ''; echo ''; echo ''; foreach ($folders as $folder) { $date = date('Y-m-d H:i:s', filemtime($folder)); $files = glob($folder . '/*.{sql,zip}', GLOB_BRACE); $total_size = 0; $file_links = []; foreach ($files as $file) { $size = filesize($file); $total_size += $size; $fname = basename($file); $nonce = wp_create_nonce('dapper_dl_' . md5($fname)); $link = admin_url('admin-post.php?action=dapper_backup_dl&f=' . urlencode($fname) . '&folder=' . urlencode(basename($folder)) . '&_wpnonce=' . $nonce); $file_links[] = '' . esc_html($fname) . ' (' . size_format($size) . ')'; } $delete_nonce = wp_create_nonce('dapper_delete_' . md5(basename($folder))); $delete_link = admin_url('admin-post.php?action=dapper_delete_backup&folder=' . urlencode(basename($folder)) . '&_wpnonce=' . $delete_nonce); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
DateFilesSizeActions
' . esc_html($date) . '' . implode('
', $file_links) . '
' . size_format($total_size) . 'Delete
'; } // Secure download (updated for dated folders) add_action('admin_post_dapper_backup_dl', 'dapper_handle_backup_download'); function dapper_handle_backup_download() { if (!current_user_can('manage_options')) { wp_die('Access denied.'); } $file = isset($_GET['f']) ? basename($_GET['f']) : ''; $folder = isset($_GET['folder']) ? basename($_GET['folder']) : ''; $nonce = $_GET['_wpnonce'] ?? ''; if (!wp_verify_nonce($nonce, 'dapper_dl_' . md5($file))) { wp_die('Security check failed.'); } $path = DAPPER_BACKUP_BASE_DIR . '/' . $folder . '/' . $file; if (!file_exists($path) || !is_readable($path)) { wp_die('File not found or inaccessible.'); } // Disable output buffering & compression for large files if (ob_get_level()) ob_end_clean(); header_remove('Content-Encoding'); // Prevent gzip // Proper headers for binary download header('Content-Description: File Transfer'); header('Content-Type: application/zip'); // Force zip type header('Content-Disposition: attachment; filename="' . $file . '"'); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); // Chunked output to avoid memory issues header('Content-Length: ' . filesize($path)); // Stream file in 1 MB chunks $chunk_size = 1024 * 1024; // 1 MB $handle = fopen($path, 'rb'); if ($handle === false) { wp_die('Failed to open file for reading.'); } while (!feof($handle)) { echo fread($handle, $chunk_size); flush(); // Send chunk immediately } fclose($handle); exit; } // Delete entire dated backup folder add_action('admin_post_dapper_delete_backup', 'dapper_handle_delete_backup'); function dapper_handle_delete_backup() { if (!current_user_can('manage_options')) wp_die('Access denied.'); $folder = isset($_GET['folder']) ? basename($_GET['folder']) : ''; $nonce = $_GET['_wpnonce'] ?? ''; if (!wp_verify_nonce($nonce, 'dapper_delete_' . md5($folder))) { wp_die('Security check failed.'); } $path = DAPPER_BACKUP_BASE_DIR . '/' . $folder; if (!file_exists($path) || !is_dir($path)) { wp_die('Backup not found.'); } // Delete all files in folder array_map('unlink', glob("$path/*")); @rmdir($path); dapper_debug_log('Deleted backup folder: ' . $folder); $redirect = add_query_arg( ['page' => 'dapper-settings', 'backup_deleted' => '1'], admin_url('admin.php') ); wp_safe_redirect($redirect); exit; }