Files
bookmarkly2/api/backup.php
T
2025-09-28 16:05:53 +02:00

298 lines
13 KiB
PHP

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Set the working directory to the project root
chdir(__DIR__ . '/..');
require_once 'include/init.php';
require_once 'include/auth.php';
// Check if the user is logged in
if (!isset($_SESSION['user_id'])) {
header('Location: /login.php');
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 != "..") {
if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . DIRECTORY_SEPARATOR . $object)) {
rrmdir($dir . DIRECTORY_SEPARATOR . $object);
} else {
@unlink($dir . DIRECTORY_SEPARATOR . $object);
}
}
}
@rmdir($dir);
}
}
/**
* Creates a zip archive from a source directory.
* @param string $source The source directory.
* @param string $destination The path for the destination zip file.
* @param string &$error_message Reference to a variable to store error messages.
* @return bool True on success, false on failure.
*/
function createZipArchive($source, $destination, &$error_message) {
if (!extension_loaded('zip')) {
$error_message = 'The "zip" PHP extension is not installed or enabled.';
return false;
}
if (!file_exists($source)) {
$error_message = "Source path does not exist: " . $source;
return false;
}
$zip = new ZipArchive();
if ($zip->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;
}
}