false, 'message' => 'A fatal server error occurred.', 'error_details' => $error ]); } }); require_once 'include/init.php'; require_once 'include/auth.php'; header('Content-Type: application/json'); if (!isset($_SESSION['user_id'])) { http_response_code(401); echo json_encode(['success' => false, 'message' => 'You must be logged in.']); exit; } /** * Recursively removes a directory and its contents. * @param string $dir The directory to remove. */ function rrmdir($dir) { if (is_dir($dir)) { $objects = scandir($dir); foreach ($objects as $object) { if ($object != "." && $object != "..") { is_dir($dir . DIRECTORY_SEPARATOR . $object) ? rrmdir($dir . DIRECTORY_SEPARATOR . $object) : unlink($dir . DIRECTORY_SEPARATOR . $object); } } rmdir($dir); } } // --- Main Logic --- try { if (!isset($_FILES['backup_file']) || $_FILES['backup_file']['error'] !== UPLOAD_ERR_OK) { throw new Exception('File upload error.'); } if (!isset($_POST['target_tab_id']) || empty($_POST['target_tab_id'])) { throw new Exception('Target tab not selected.'); } $target_tab_id = (int)$_POST['target_tab_id']; $backup_file = $_FILES['backup_file']['tmp_name']; $temp_dir = sys_get_temp_dir() . '/bookmarkly_import_' . uniqid(); if (!mkdir($temp_dir, 0777, true)) { throw new Exception('Failed to create temporary directory.'); } $zip = new ZipArchive; if ($zip->open($backup_file) !== TRUE) { rrmdir($temp_dir); throw new Exception('Failed to open the backup file.'); } $zip->extractTo($temp_dir); $zip->close(); $backup_db_path = $temp_dir . '/database/bookmarkly.db'; $manifest_path = $temp_dir . '/manifest.json'; if (!file_exists($backup_db_path)) { throw new Exception('Invalid backup file: bookmarkly.db not found.'); } $backup_pdo = new PDO('sqlite:' . $backup_db_path); $backup_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $backup_pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // --- Begin Transaction --- $pdo->beginTransaction(); try { $is_tab_backup = false; if (file_exists($manifest_path)) { $manifest = json_decode(file_get_contents($manifest_path), true); if (isset($manifest['type']) && $manifest['type'] === 'tab') { $is_tab_backup = true; } } // --- Import Logic --- $old_to_new_category_id_map = []; $imported_categories_count = 0; $imported_bookmarks_count = 0; // If it's a tab backup, also import tab settings and files if ($is_tab_backup) { $backup_tab = $backup_pdo->query("SELECT * FROM tabs LIMIT 1")->fetch(); if ($backup_tab) { // Handle background image $new_background_url = $backup_tab['background_image_url']; if (!empty($new_background_url) && strpos($new_background_url, 'images/backgrounds/') === 0) { $source_bg = $temp_dir . '/' . $new_background_url; if (file_exists($source_bg)) { $bg_filename = basename($new_background_url); $dest_dir = 'data/images/backgrounds/'; if (!is_dir($dest_dir)) mkdir($dest_dir, 0755, true); $unique_filename = 'tab' . $target_tab_id . '_' . time() . '_' . $bg_filename; $dest_bg = $dest_dir . $unique_filename; if (copy($source_bg, $dest_bg)) { $new_background_url = $dest_bg . '?v=' . time(); } } } // Update target tab with settings from backup $update_tab_stmt = $pdo->prepare( "UPDATE tabs SET theme = ?, theme_transparency = ?, background_image_url = ?, background_brightness = ?, background_saturation = ? WHERE id = ?" ); $update_tab_stmt->execute([ $backup_tab['theme'], $backup_tab['theme_transparency'], $new_background_url, $backup_tab['background_brightness'], $backup_tab['background_saturation'], $target_tab_id ]); } } // Import Categories $categories = $backup_pdo->query("SELECT * FROM categories")->fetchAll(); $stmt_insert_category = $pdo->prepare("INSERT INTO categories (name, sort_order, tab_id) VALUES (?, ?, ?)"); foreach ($categories as $category) { $stmt_insert_category->execute([$category['name'], $category['sort_order'], $target_tab_id]); $old_to_new_category_id_map[$category['id']] = $pdo->lastInsertId(); $imported_categories_count++; } // Import Bookmarks $bookmarks = $backup_pdo->query("SELECT * FROM bookmarks")->fetchAll(); $stmt_insert_bookmark = $pdo->prepare("INSERT INTO bookmarks (category_id, title, url, icon, is_favorite, sort_order, tab_id) VALUES (?, ?, ?, ?, ?, ?, ?)"); foreach ($bookmarks as $bookmark) { // Determine the new category ID, allowing for bookmarks without a category $new_category_id = null; if (!empty($bookmark['category_id']) && isset($old_to_new_category_id_map[$bookmark['category_id']])) { $new_category_id = $old_to_new_category_id_map[$bookmark['category_id']]; } // Handle icon $new_icon_path = $bookmark['icon']; // In tab backups, icon paths are relative to the zip root, e.g., 'images/custom/icon.png' if ($is_tab_backup && !empty($new_icon_path) && strpos($new_icon_path, 'images/custom/') === 0) { $source_icon = $temp_dir . '/' . $new_icon_path; if (file_exists($source_icon)) { $icon_filename = basename($new_icon_path); $dest_dir = 'data/images/custom/'; // The correct destination for custom icons if (!is_dir($dest_dir)) mkdir($dest_dir, 0755, true); $unique_filename = 'imported_' . time() . '_' . $icon_filename; $dest_icon = $dest_dir . $unique_filename; if (copy($source_icon, $dest_icon)) { $new_icon_path = $dest_icon; } } } $stmt_insert_bookmark->execute([ $new_category_id, $bookmark['title'], $bookmark['url'], $new_icon_path, $bookmark['is_favorite'], $bookmark['sort_order'], $target_tab_id ]); $imported_bookmarks_count++; } $pdo->commit(); $message = "Import successful. Added {$imported_categories_count} categories and {$imported_bookmarks_count} bookmarks."; echo json_encode(['success' => true, 'message' => $message]); } catch (Exception $e) { $pdo->rollBack(); throw new Exception('Database error during import: ' . $e->getMessage()); } } catch (Exception $e) { http_response_code(400); echo json_encode(['success' => false, 'message' => $e->getMessage()]); } finally { if (isset($temp_dir) && is_dir($temp_dir)) { rrmdir($temp_dir); } // Close DB connections $pdo = null; if(isset($backup_pdo)) { $backup_pdo = null; } }