298 lines
13 KiB
PHP
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;
|
|
}
|
|
}
|