open($destination, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { $error_message = 'Failed to create zip archive. Check server permissions and logs.'; return false; } $source = realpath($source); if (is_dir($source)) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($files as $file) { $file = realpath($file); $relativePath = str_replace($source . DIRECTORY_SEPARATOR, '', $file); if (is_dir($file)) { $zip->addEmptyDir($relativePath); } else if (is_file($file)) { $zip->addFromString($relativePath, file_get_contents($file)); } } } if (!$zip->close()) { $error_message = "Failed to write and close the zip archive. Check for disk space and permissions."; return false; } return true; } $projectRoot = realpath(__DIR__ . '/..'); // --- Main Logic --- $tab_id = $_GET['tab_id'] ?? 'full'; $error_message = ''; if ($tab_id === 'full') { // --- Full Backup Logic --- $dataDir = 'data'; $backupFileName = 'bookmarkly_full_backup_' . date('Y-m-d_H-i-s') . '.zip'; $backupFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $backupFileName; if (createZipArchive($dataDir, $backupFilePath, $error_message)) { header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . $backupFileName . '"'); header('Content-Length: ' . filesize($backupFilePath)); readfile($backupFilePath); unlink($backupFilePath); exit; } else { $_SESSION['flash_message'] = ['text' => 'Failed to create full backup. Reason: ' . $error_message, 'type' => 'danger']; header('Location: ../settings.php'); exit; } } else { // --- Tab-specific Backup Logic --- $tab_id = filter_var($tab_id, FILTER_VALIDATE_INT); if ($tab_id === false) { $_SESSION['flash_message'] = ['text' => 'Invalid Tab ID.', 'type' => 'danger']; header('Location: ../settings.php'); exit; } $temp_dir = sys_get_temp_dir() . '/bookmarkly_tab_backup_' . uniqid(); if (!mkdir($temp_dir, 0777, true)) { $_SESSION['flash_message'] = ['text' => 'Failed to create temporary directory for backup.', 'type' => 'danger']; header('Location: ../settings.php'); exit; } try { // --- START DEBUGGING --- $log_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'bookmarkly_backup_debug.log'; $log_file = fopen($log_path, 'w'); if ($log_file) { fwrite($log_file, "--- Starting Tab Backup Debug ---\n"); fwrite($log_file, "Log file location: " . $log_path . "\n"); fwrite($log_file, "Time: " . date('c') . "\n"); fwrite($log_file, "Project Root: " . $projectRoot . "\n\n"); } // --- END DEBUGGING --- // 1. Fetch data for the specific tab from the main DB $tab_stmt = $pdo->prepare("SELECT * FROM tabs WHERE id = ?"); $tab_stmt->execute([$tab_id]); $tab = $tab_stmt->fetch(PDO::FETCH_ASSOC); if (!$tab) { throw new Exception("Tab with ID {$tab_id} not found."); } $cat_stmt = $pdo->prepare("SELECT * FROM categories WHERE tab_id = ?"); $cat_stmt->execute([$tab_id]); $categories = $cat_stmt->fetchAll(PDO::FETCH_ASSOC); $book_stmt = $pdo->prepare("SELECT * FROM bookmarks WHERE tab_id = ?"); $book_stmt->execute([$tab_id]); $bookmarks = $book_stmt->fetchAll(PDO::FETCH_ASSOC); // 2. Create temporary structure and database $temp_db_path = $temp_dir . '/database/bookmarkly.db'; mkdir($temp_dir . '/database', 0777, true); mkdir($temp_dir . '/images/custom', 0777, true); // CORRECTED: Create the 'custom' directory mkdir($temp_dir . '/images/backgrounds', 0777, true); $backup_pdo = new PDO('sqlite:' . $temp_db_path); $backup_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Create schema in backup DB $backup_pdo->exec("CREATE TABLE tabs (id INTEGER PRIMARY KEY, name TEXT, background_image_url TEXT, background_brightness INTEGER, background_saturation INTEGER, theme TEXT, theme_transparency INTEGER)"); $backup_pdo->exec("CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT, sort_order INTEGER, tab_id INTEGER)"); $backup_pdo->exec("CREATE TABLE bookmarks (id INTEGER PRIMARY KEY, category_id INTEGER, title TEXT, url TEXT, icon TEXT, is_favorite INTEGER, sort_order INTEGER, tab_id INTEGER)"); // 3. Copy files and insert data into backup DB // Handle Tab Background $new_background_path = $tab['background_image_url']; if (!empty($tab['background_image_url']) && strpos($tab['background_image_url'], 'data/images/backgrounds/') === 0) { $source_path_relative = strtok($tab['background_image_url'], '?'); $source_path_absolute = $projectRoot . DIRECTORY_SEPARATOR . $source_path_relative; if (file_exists($source_path_absolute)) { $bg_filename = basename($source_path_relative); $new_background_path_in_db = 'images/backgrounds/' . $bg_filename; $destination_path = $temp_dir . DIRECTORY_SEPARATOR . $new_background_path_in_db; if (copy($source_path_absolute, $destination_path)) { $new_background_path = $new_background_path_in_db; } } } // Insert Tab data $insert_tab_stmt = $backup_pdo->prepare("INSERT INTO tabs VALUES (?, ?, ?, ?, ?, ?, ?)"); $insert_tab_stmt->execute([$tab['id'], $tab['name'], $new_background_path, $tab['background_brightness'], $tab['background_saturation'], $tab['theme'], $tab['theme_transparency']]); // Handle Categories and Bookmarks $insert_cat_stmt = $backup_pdo->prepare("INSERT INTO categories VALUES (?, ?, ?, ?)"); foreach ($categories as $category) { $insert_cat_stmt->execute([$category['id'], $category['name'], $category['sort_order'], $category['tab_id']]); } $insert_book_stmt = $backup_pdo->prepare("INSERT INTO bookmarks VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); foreach ($bookmarks as $bookmark) { $icon_path_for_db = $bookmark['icon']; // Start with the original path // Only process locally stored custom icons if (!empty($bookmark['icon']) && strpos($bookmark['icon'], 'data/images/custom/') === 0) { // --- START DEBUGGING --- if ($log_file) fwrite($log_file, "Processing bookmark ID: " . $bookmark['id'] . " Title: '" . $bookmark['title'] . "'\n"); // --- END DEBUGGING --- $relative_source_path = strtok($bookmark['icon'], '?'); $absolute_source_path = $projectRoot . DIRECTORY_SEPARATOR . $relative_source_path; // --- START DEBUGGING --- if ($log_file) { fwrite($log_file, " - DB Path: " . $bookmark['icon'] . "\n"); fwrite($log_file, " - Relative Path Used: " . $relative_source_path . "\n"); fwrite($log_file, " - Absolute Path Checked: " . $absolute_source_path . "\n"); $file_exists_result = file_exists($absolute_source_path); fwrite($log_file, " - file_exists() Result: " . ($file_exists_result ? 'TRUE' : 'FALSE') . "\n"); } // --- END DEBUGGING --- if ($file_exists_result) { $filename = basename($relative_source_path); // The new path inside the zip archive's structure $relative_dest_path = 'images/custom/' . $filename; // The absolute path to copy the file to in the temp directory $absolute_dest_path = $temp_dir . DIRECTORY_SEPARATOR . $relative_dest_path; // Copy the file and if successful, update the path to be stored in the backup DB if (copy($absolute_source_path, $absolute_dest_path)) { $icon_path_for_db = $relative_dest_path; if ($log_file) fwrite($log_file, " - ACTION: File copied successfully.\n\n"); } else { if ($log_file) fwrite($log_file, " - ACTION: copy() FAILED.\n\n"); } } else { if ($log_file) fwrite($log_file, " - ACTION: File skipped.\n\n"); } } $insert_book_stmt->execute([$bookmark['id'], $bookmark['category_id'], $bookmark['title'], $bookmark['url'], $icon_path_for_db, $bookmark['is_favorite'], $bookmark['sort_order'], $bookmark['tab_id']]); } // 4. Create manifest.json $manifest = [ 'type' => 'tab', 'tab_id' => $tab['id'], 'tab_name' => $tab['name'], 'version' => '2.0', 'timestamp' => date('c') ]; file_put_contents($temp_dir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT)); // 5. Zip and send the backup $safe_tab_name = preg_replace('/[^a-zA-Z0-9_-]/', '_', $tab['name']); $backupFileName = "bookmarkly_tab_{$safe_tab_name}_backup_" . date('Y-m-d') . '.zip'; $backupFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $backupFileName; if (createZipArchive($temp_dir, $backupFilePath, $error_message)) { // --- START DEBUGGING --- if ($log_file) { fwrite($log_file, "--- Backup zip created successfully. ---"); fclose($log_file); } // --- END DEBUGGING --- header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . $backupFileName . '"'); header('Content-Length: ' . filesize($backupFilePath)); readfile($backupFilePath); unlink($backupFilePath); rrmdir($temp_dir); exit; } else { // --- START DEBUGGING --- if ($log_file) { fwrite($log_file, "--- FAILED to create zip archive. Error: " . $error_message . " ---"); fclose($log_file); } // --- END DEBUGGING --- throw new Exception('Failed to create zip archive. ' . $error_message); } } catch (Exception $e) { // --- START DEBUGGING --- if (isset($log_file) && is_resource($log_file)) { fwrite($log_file, "--- CATCH BLOCK ERROR: " . $e->getMessage() . " ---"); fclose($log_file); } // --- END DEBUGGING --- rrmdir($temp_dir); // Clean up on error $_SESSION['flash_message'] = ['text' => 'Error creating tab backup: ' . $e->getMessage(), 'type' => 'danger']; header('Location: ../settings.php'); exit; } }