Files
2025-10-07 10:12:17 +02:00

733 lines
47 KiB
PHP

<?php
$page_title = 'Settings - Bookmarkly';
// Add settings-page class to body for CSS targeting
$GLOBALS['body_class'] = 'settings-page';
require_once 'include/auth.php';
// Flash messages for feedback
$message = '';
$message_type = '';
if (isset($_SESSION['flash_message'])) {
$message = $_SESSION['flash_message']['text'];
$message_type = $_SESSION['flash_message']['type'];
unset($_SESSION['flash_message']);
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'update_settings') {
$upsert_stmt = $pdo->prepare("REPLACE INTO settings (key, value) VALUES (?, ?)");
// Loop through all possible keys from the form to save them
$setting_keys = ['site_title', 'remember_me_duration', 'language', 'favicon_extractor_url', 'logo_height'];
foreach ($setting_keys as $key) {
if (isset($_POST[$key])) {
$upsert_stmt->execute([$key, $_POST[$key]]);
}
}
// Handle checkboxes separately, as they are not sent when unchecked
$checkboxes = ['display_site_title', 'open_new_tab', 'dashboard_protected', 'show_search_bar'];
foreach ($checkboxes as $key) {
$upsert_stmt->execute([$key, isset($_POST[$key]) ? '1' : '0']);
}
// Handle file uploads moved from 'update_theme'
$uploadDir = 'data/images/custom/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$allowed_logo_ext = ['png', 'jpg', 'jpeg', 'gif', 'svg'];
$allowed_favicon_ext = ['png', 'ico', 'svg'];
// Handle Logo Upload
if (isset($_FILES['site_logo']) && $_FILES['site_logo']['error'] === UPLOAD_ERR_OK) {
$logo_ext = strtolower(pathinfo($_FILES['site_logo']['name'], PATHINFO_EXTENSION));
if (in_array($logo_ext, $allowed_logo_ext)) {
// To avoid caching issues, append a timestamp to the filename
$logo_filename = 'logo.' . $logo_ext;
$logo_path = $uploadDir . $logo_filename;
if (move_uploaded_file($_FILES['site_logo']['tmp_name'], $logo_path)) {
$upsert_stmt->execute(['site_logo', $logo_path . '?v=' . time()]);
} else {
$_SESSION['flash_message'] = ['text' => 'Failed to move uploaded logo.', 'type' => 'danger'];
}
} else {
$_SESSION['flash_message'] = ['text' => 'Invalid file type for logo.', 'type' => 'danger'];
}
}
// Handle Favicon Upload
if (isset($_FILES['site_favicon']) && $_FILES['site_favicon']['error'] === UPLOAD_ERR_OK) {
$favicon_ext = strtolower(pathinfo($_FILES['site_favicon']['name'], PATHINFO_EXTENSION));
if (in_array($favicon_ext, $allowed_favicon_ext)) {
$favicon_filename = 'favicon.' . $favicon_ext;
$favicon_path = $uploadDir . $favicon_filename;
if (move_uploaded_file($_FILES['site_favicon']['tmp_name'], $favicon_path)) {
$upsert_stmt->execute(['site_favicon', $favicon_path . '?v=' . time()]);
} else {
$_SESSION['flash_message'] = ['text' => 'Failed to move uploaded favicon.', 'type' => 'danger'];
}
} else {
$_SESSION['flash_message'] = ['text' => 'Invalid file type for favicon.', 'type' => 'danger'];
}
}
if (!isset($_SESSION['flash_message'])) {
$_SESSION['flash_message'] = ['text' => translate('settings_save_success'), 'type' => 'success'];
}
} elseif ($action === 'update_user') {
$username = trim($_POST['username'] ?? '');
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
$user_id = $_SESSION['user_id'];
if (empty($username)) {
$_SESSION['flash_message'] = ['text' => 'Username cannot be empty.', 'type' => 'warning'];
} else {
// Check if password needs to be updated
if (!empty($new_password)) {
if ($new_password === $confirm_password) {
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET username = ?, password = ? WHERE id = ?");
$stmt->execute([$username, $hashed_password, $user_id]);
$_SESSION['flash_message'] = ['text' => translate('settings_user_update_success'), 'type' => 'success'];
$_SESSION['username'] = $username; // Update session username
} else {
$_SESSION['flash_message'] = ['text' => translate('settings_password_mismatch'), 'type' => 'danger'];
}
} else {
// Update username only
$stmt = $pdo->prepare("UPDATE users SET username = ? WHERE id = ?");
$stmt->execute([$username, $user_id]);
$_SESSION['flash_message'] = ['text' => translate('settings_user_update_success'), 'type' => 'success'];
$_SESSION['username'] = $username; // Update session username
}
}
} elseif ($action === 'update_theme') {
$upsert_stmt = $pdo->prepare("REPLACE INTO settings (key, value) VALUES (?, ?)");
$upload_error = false;
// Handle file uploads
$backgroundUploadDir = 'data/images/backgrounds/';
if (!is_dir($backgroundUploadDir)) {
mkdir($backgroundUploadDir, 0755, true);
}
// Handle Background Image Upload
if (isset($_FILES['background_image']) && $_FILES['background_image']['error'] === UPLOAD_ERR_OK) {
$bg_info = getimagesize($_FILES['background_image']['tmp_name']);
if ($bg_info !== false) { // It's an image
// Find and delete any existing global background
$existing_files = glob($backgroundUploadDir . 'global-bg.*');
foreach ($existing_files as $file_to_delete) {
unlink($file_to_delete);
}
$bg_ext = strtolower(pathinfo($_FILES['background_image']['name'], PATHINFO_EXTENSION));
$bg_filename = 'global-bg.' . $bg_ext;
$bg_path = $backgroundUploadDir . $bg_filename;
if (move_uploaded_file($_FILES['background_image']['tmp_name'], $bg_path)) {
// Save the new path with a cache-busting query string
$_POST['background_image_url'] = $bg_path . '?v=' . time();
} else {
$_SESSION['flash_message'] = ['text' => 'Failed to move uploaded background image.', 'type' => 'danger'];
$upload_error = true;
}
} else {
$_SESSION['flash_message'] = ['text' => 'Invalid file type for background. Please upload an image.', 'type' => 'danger'];
$upload_error = true;
}
}
$theme_keys = ['theme', 'background_image_url', 'background_brightness', 'background_saturation', 'pexels_api_key', 'custom_css', 'theme_transparency', 'custom_footer_html'];
foreach ($theme_keys as $key) {
if (isset($_POST[$key])) {
$upsert_stmt->execute([$key, trim($_POST[$key])]);
}
}
if (!$upload_error) {
$_SESSION['flash_message'] = ['text' => 'Theme settings saved successfully.', 'type' => 'success'];
}
} elseif ($action === 'restore_backup') {
if (isset($_FILES['backup_file']) && $_FILES['backup_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['backup_file']['tmp_name'];
$zip = new ZipArchive;
if ($zip->open($file) === TRUE) {
// Check for essential files/directories in the zip
if ($zip->locateName('database/bookmarkly.db') !== false) {
// Correct paths for scripts running in the root directory
$dataDir = 'data';
$extractPath = 'data/';
// Important: ensure the old data is removed or backed up before extraction
// A more robust solution would backup the current 'data' dir first.
// Clear the data directory before restoring, only if it exists
if (is_dir($dataDir)) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dataDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDir()) {
@rmdir($fileinfo->getRealPath());
} else {
@unlink($fileinfo->getRealPath());
}
}
}
// Ensure the target directory exists
if (!is_dir($extractPath)) {
mkdir($extractPath, 0755, true);
}
// Extract the new data
$zip->extractTo($extractPath);
$zip->close();
session_destroy();
$_SESSION['flash_message'] = ['text' => translate('settings_backup_restore_success'), 'type' => 'success'];
header('Location: login.php');
exit;
} else {
$_SESSION['flash_message'] = ['text' => translate('settings_backup_restore_failed'), 'type' => 'danger'];
}
$zip->close();
} else {
$_SESSION['flash_message'] = ['text' => translate('settings_backup_restore_failed'), 'type' => 'danger'];
}
} else {
$_SESSION['flash_message'] = ['text' => translate('settings_backup_restore_failed'), 'type' => 'danger'];
}
}
// Redirect back to settings page for all actions except a successful restore
header('Location: settings.php');
exit;
}
// Fetch tabs for the new import form
$tabs_stmt = $pdo->prepare("SELECT id, name FROM tabs");
$tabs_stmt->execute();
$tabs = $tabs_stmt->fetchAll();
require_once 'include/header.php';
?>
<div class="container mt-4">
<h1 class="mb-4"><?php echo translate('settings_title'); ?></h1>
<?php if ($message): ?>
<div class="alert alert-<?php echo $message_type; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($message); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="row">
<div class="col-md-2">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<button class="nav-link active" id="v-pills-general-tab" data-bs-toggle="pill" data-bs-target="#v-pills-general" type="button" role="tab"><?php echo translate('settings_general_tab'); ?></button>
<button class="nav-link" id="v-pills-theme-tab" data-bs-toggle="pill" data-bs-target="#v-pills-theme" type="button" role="tab"><?php echo translate('settings_theme_tab'); ?></button>
<button class="nav-link" id="v-pills-backup-tab" data-bs-toggle="pill" data-bs-target="#v-pills-backup" type="button" role="tab"><?php echo translate('settings_backup_tab'); ?></button>
<?php if (isAdmin()): ?>
<button class="nav-link" id="v-pills-system-tab" data-bs-toggle="pill" data-bs-target="#v-pills-system" type="button" role="tab">System Maintenance</button>
<?php endif; ?>
</div>
</div>
<div class="col-md-10">
<div class="tab-content" id="v-pills-tabContent">
<!-- General Settings Tab -->
<div class="tab-pane fade show active" id="v-pills-general" role="tabpanel">
<div class="card">
<div class="card-header">
<h4><?php echo translate('settings_general_title'); ?></h4>
</div>
<div class="card-body">
<form action="settings.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="update_settings">
<div class="mb-3">
<label for="site_title" class="form-label">Site Title</label>
<input type="text" class="form-control" id="site_title" name="site_title" value="<?php echo htmlspecialchars($settings['site_title'] ?? 'Bookmarkly'); ?>">
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="display_site_title" name="display_site_title" value="1" <?php echo ($settings['display_site_title'] ?? '1') === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="display_site_title">Display Site Title</label>
</div>
<div class="mb-3">
<label for="site_logo" class="form-label">Site Logo</label>
<input type="file" class="form-control" id="site_logo" name="site_logo">
<?php if (!empty($settings['site_logo'])): ?>
<small class="form-text text-muted d-block mt-2">Current logo: <img src="<?php echo htmlspecialchars($settings['site_logo']); ?>" alt="Current Logo" style="max-height: 24px; vertical-align: middle;"></small>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="logo_height" class="form-label">Logo Height: <span id="logo_height_value"><?php echo htmlspecialchars($settings['logo_height'] ?? '30'); ?>px</span></label>
<input type="range" class="form-range" min="10" max="50" id="logo_height" name="logo_height" value="<?php echo htmlspecialchars($settings['logo_height'] ?? '30'); ?>" oninput="document.getElementById('logo_height_value').textContent = this.value + 'px';">
</div>
<div class="mb-3">
<label for="site_favicon" class="form-label">Site Favicon</label>
<input type="file" class="form-control" id="site_favicon" name="site_favicon">
<?php if (!empty($settings['site_favicon'])): ?>
<small class="form-text text-muted d-block mt-2">Current favicon: <img src="<?php echo htmlspecialchars($settings['site_favicon']); ?>" alt="Current Favicon" style="max-height: 16px; vertical-align: middle;"></small>
<?php endif; ?>
</div>
<hr>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="open_new_tab" name="open_new_tab" value="1" <?php echo ($settings['open_new_tab'] ?? '0') === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="open_new_tab"><?php echo translate('settings_open_new_tab'); ?></label>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="dashboard_protected" name="dashboard_protected" value="1" <?php echo ($settings['dashboard_protected'] ?? '0') === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="dashboard_protected"><?php echo translate('settings_dashboard_protection'); ?></label>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="show_search_bar" name="show_search_bar" value="1" <?php echo ($settings['show_search_bar'] ?? '1') === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="show_search_bar"><?php echo translate('settings_show_search_bar'); ?></label>
</div>
<div class="mb-3">
<label for="remember_me_duration" class="form-label"><?php echo translate('settings_stay_logged_in'); ?></label>
<select class="form-select" id="remember_me_duration" name="remember_me_duration">
<option <?php echo ($settings['remember_me_duration'] ?? '') === '1 day' ? 'selected' : ''; ?>>1 day</option>
<option <?php echo ($settings['remember_me_duration'] ?? '') === '2 weeks' ? 'selected' : ''; ?>>2 weeks</option>
<option <?php echo ($settings['remember_me_duration'] ?? '') === '1 month' ? 'selected' : ''; ?>>1 month</option>
<option <?php echo ($settings['remember_me_duration'] ?? '') === '3 months' ? 'selected' : ''; ?>>3 months</option>
<option <?php echo ($settings['remember_me_duration'] ?? '') === '1 year' ? 'selected' : ''; ?>>1 year</option>
</select>
</div>
<div class="mb-3">
<label for="language" class="form-label"><?php echo translate('settings_language'); ?></label>
<select class="form-select" id="language" name="language">
<option value="en" <?php echo ($settings['language'] ?? 'en') === 'en' ? 'selected' : ''; ?>>English</option>
<option value="nl" <?php echo ($settings['language'] ?? 'en') === 'nl' ? 'selected' : ''; ?>>Nederlands</option>
</select>
</div>
<div class="mb-3">
<label for="favicon_extractor_url" class="form-label"><?php echo translate('settings_favicon_extractor_url'); ?></label>
<input type="url" class="form-control" id="favicon_extractor_url" name="favicon_extractor_url" value="<?php echo htmlspecialchars($settings['favicon_extractor_url'] ?? ''); ?>" placeholder="http://localhost:3000">
<div class="form-text"><?php echo translate('settings_favicon_extractor_url_help'); ?></div>
</div>
<button type="submit" class="btn btn-primary"><?php echo translate('settings_save_settings_button'); ?></button>
</form>
<hr>
<h5><?php echo translate('settings_user_account_title'); ?></h5>
<form action="settings.php" method="POST">
<input type="hidden" name="action" value="update_user">
<div class="mb-3">
<label for="username" class="form-label"><?php echo translate('username'); ?></label>
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($_SESSION['username'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="new_password" class="form-label"><?php echo translate('settings_new_password'); ?></label>
<input type="password" class="form-control" id="new_password" name="new_password">
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label"><?php echo translate('settings_confirm_password'); ?></label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password">
</div>
<button type="submit" class="btn btn-primary"><?php echo translate('settings_update_profile_button'); ?></button>
</form>
</div>
</div>
</div>
<!-- Theme & Background Tab -->
<div class="tab-pane fade" id="v-pills-theme" role="tabpanel">
<div class="card">
<div class="card-header">
<h4><?php echo translate('settings_theme_title'); ?></h4>
</div>
<div class="card-body">
<p class="text-muted">These settings serve as the global default. They can be overridden by individual tabs in the <a href="tabs_settings.php">Tab Management</a> page.</p>
<hr>
<form action="settings.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="update_theme">
<div class="mb-3">
<label for="theme" class="form-label"><?php echo translate('settings_theme'); ?></label>
<select class="form-select" id="theme" name="theme">
<option value="light" <?php echo ($settings['theme'] ?? 'light') === 'light' ? 'selected' : ''; ?>><?php echo translate('settings_theme_light'); ?></option>
<option value="dark" <?php echo ($settings['theme'] ?? 'light') === 'dark' ? 'selected' : ''; ?>><?php echo translate('settings_theme_dark'); ?></option>
<option value="transparent" <?php echo ($settings['theme'] ?? 'light') === 'transparent' ? 'selected' : ''; ?>><?php echo translate('settings_theme_transparent'); ?></option>
</select>
</div>
<div class="mb-3" id="transparency-slider-container">
<label for="theme_transparency" class="form-label"><?php echo translate('settings_theme_transparency'); ?>: <span id="theme_transparency_value"><?php echo htmlspecialchars($settings['theme_transparency'] ?? '85'); ?>%</span></label>
<input type="range" class="form-range" min="0" max="100" id="theme_transparency" name="theme_transparency" value="<?php echo htmlspecialchars($settings['theme_transparency'] ?? '85'); ?>" oninput="document.getElementById('theme_transparency_value').textContent = this.value + '%';">
</div>
<div class="mb-3">
<label for="background_image" class="form-label"><?php echo translate('settings_background_upload'); ?></label>
<input type="file" class="form-control" id="background_image" name="background_image" accept="image/*">
<?php
$current_bg = $settings['background_image_url'] ?? '';
// Sanitize path for file_exists check
$bg_path_for_check = strtok($current_bg, '?');
if (!empty($current_bg) && file_exists($bg_path_for_check) && strpos($bg_path_for_check, 'data/images/backgrounds/') === 0):
?>
<small class="form-text text-muted d-block mt-2">
<?php echo translate('settings_background_current'); ?> <a href="<?php echo htmlspecialchars($current_bg); ?>" target="_blank"><?php echo basename($bg_path_for_check); ?></a>
<img src="<?php echo htmlspecialchars($current_bg); ?>" alt="Current Background" style="max-height: 40px; vertical-align: middle; margin-left: 10px;">
<button type="button" class="btn btn-sm btn-secondary ms-2" onclick="optimizeImage('<?php echo htmlspecialchars($current_bg); ?>')"><?php echo translate('settings_background_optimize'); ?></button>
</small>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="background_image_url" class="form-label"><?php echo translate('settings_background_url'); ?></label>
<input type="text" class="form-control" id="background_image_url" name="background_image_url" value="<?php echo htmlspecialchars($settings['background_image_url'] ?? ''); ?>" placeholder="Enter image URL or search Pexels">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="background_brightness" class="form-label"><?php echo translate('settings_background_brightness'); ?>: <span id="background_brightness_value"><?php echo htmlspecialchars($settings['background_brightness'] ?? '100'); ?>%</span></label>
<input type="range" class="form-range" min="0" max="200" id="background_brightness" name="background_brightness" value="<?php echo htmlspecialchars($settings['background_brightness'] ?? '100'); ?>" oninput="document.getElementById('background_brightness_value').textContent = this.value + '%';">
</div>
<div class="col-md-6 mb-3">
<label for="background_saturation" class="form-label"><?php echo translate('settings_background_saturation'); ?>: <span id="background_saturation_value"><?php echo htmlspecialchars($settings['background_saturation'] ?? '100'); ?>%</span></label>
<input type="range" class="form-range" min="0" max="200" id="background_saturation" name="background_saturation" value="<?php echo htmlspecialchars($settings['background_saturation'] ?? '100'); ?>" oninput="document.getElementById('background_saturation_value').textContent = this.value + '%';">
</div>
</div>
<hr>
<h5><?php echo translate('settings_pexels_integration'); ?></h5>
<div class="mb-3">
<label for="pexels_api_key" class="form-label"><?php echo translate('settings_pexels_api_key'); ?></label>
<input type="password" class="form-control" id="pexels_api_key" name="pexels_api_key" value="<?php echo htmlspecialchars($settings['pexels_api_key'] ?? ''); ?>" placeholder="Enter your Pexels API key">
</div>
<div class="input-group mb-3">
<input type="text" id="pexels_search_query" class="form-control" placeholder="<?php echo translate('settings_pexels_search_placeholder'); ?>" <?php echo empty($settings['pexels_api_key']) ? 'disabled' : ''; ?>>
<button class="btn btn-secondary" type="button" id="pexels_search_button" <?php echo empty($settings['pexels_api_key']) ? 'disabled' : ''; ?>>
<i class="bi bi-search"></i> <?php echo translate('settings_pexels_search_button'); ?>
</button>
</div>
<div id="pexels_results" class="row row-cols-3 g-2"></div>
<hr>
<h5><?php echo translate('settings_custom_css'); ?></h5>
<div class="mb-3">
<label for="custom_css" class="form-label"><?php echo translate('settings_custom_css'); ?></label>
<textarea class="form-control" id="custom_css" name="custom_css" rows="5"><?php echo htmlspecialchars($settings['custom_css'] ?? ''); ?></textarea>
</div>
<hr>
<h5><?php echo translate('settings_custom_footer'); ?></h5>
<div class="mb-3">
<label for="custom_footer_html" class="form-label"><?php echo translate('settings_custom_footer'); ?></label>
<textarea class="form-control" id="custom_footer_html" name="custom_footer_html" rows="5"><?php echo htmlspecialchars($settings['custom_footer_html'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary"><?php echo translate('settings_save_theme_button'); ?></button>
</form>
</div>
</div>
</div>
<!-- Backup & Restore Tab -->
<div class="tab-pane fade" id="v-pills-backup" role="tabpanel">
<div class="card">
<div class="card-header">
<h4><?php echo translate('settings_backup_title'); ?></h4>
</div>
<div class="card-body">
<h5><?php echo translate('settings_backup_create_title'); ?></h5>
<p><?php echo translate('settings_backup_text'); ?></p>
<form action="api/backup.php" method="GET" class="mb-4">
<div class="input-group">
<select class="form-select" name="tab_id" id="backup_tab_id">
<option value="full"><?php echo translate('settings_backup_full'); ?></option>
<?php foreach ($tabs as $tab): ?>
<option value="<?php echo htmlspecialchars($tab['id']); ?>">
<?php echo translate('settings_backup_tab_prefix'); ?>: <?php echo htmlspecialchars($tab['name']); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary"><?php echo translate('settings_backup_create_button'); ?></button>
</div>
</form>
<hr>
<h5><?php echo translate('settings_backup_restore_title'); ?></h5>
<p><?php echo translate('settings_backup_restore_text'); ?></p>
<form action="settings.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="restore_backup">
<div class="mb-3">
<label for="backup_file" class="form-label"><?php echo translate('settings_backup_restore_input'); ?></label>
<input class="form-control" type="file" id="backup_file" name="backup_file" accept=".zip" required>
</div>
<button type="submit" class="btn btn-warning"><?php echo translate('settings_backup_restore_button'); ?></button>
</form>
<hr class="my-4">
<h5><?php echo translate('settings_import_title'); ?></h5>
<p><?php echo translate('settings_import_text'); ?></p>
<form action="api/import_backup.php" method="POST" enctype="multipart/form-data" id="import-backup-form">
<div class="mb-3">
<label for="import_backup_file" class="form-label"><?php echo translate('settings_import_input'); ?></label>
<input class="form-control" type="file" id="import_backup_file" name="backup_file" accept=".zip" required>
</div>
<div class="mb-3">
<label for="target_tab_id" class="form-label"><?php echo translate('settings_import_select_tab'); ?></label>
<select class="form-select" id="target_tab_id" name="target_tab_id" required>
<option value=""><?php echo translate('settings_import_select_tab_placeholder'); ?></option>
<?php foreach ($tabs as $tab): ?>
<option value="<?php echo htmlspecialchars($tab['id']); ?>">
<?php echo htmlspecialchars($tab['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-info"><?php echo translate('settings_import_button'); ?></button>
</form>
</div>
</div>
</div>
<!-- System Tab -->
<?php // Temporarily disabled admin check for testing: if (isAdmin()): ?>
<div class="tab-pane fade" id="v-pills-system" role="tabpanel">
<div class="card">
<div class="card-header">
<h4>System Maintenance</h4>
<small class="text-muted">Debug: Admin role detected</small>
</div>
<div class="card-body">
<h5>Update Database Schema</h5>
<p>If new features require database changes, you can apply them here. This process is safe to run multiple times.</p>
<button class="btn btn-info" id="update-db-button">Update Database</button>
<div id="update-db-result" class="mt-3"></div>
<hr class="my-4">
<h5><?php echo translate('settings_maintenance_fix_orphaned_bookmarks_title'); ?></h5>
<p><?php echo translate('settings_maintenance_fix_orphaned_bookmarks_desc'); ?></p>
<button class="btn btn-warning" id="fix-bookmarks-button"><?php echo translate('settings_maintenance_fix_orphaned_bookmarks_button'); ?></button>
<div id="fix-bookmarks-result" class="mt-3"></div>
<hr class="my-4">
<h5>Fix Public Tabs Visibility</h5>
<p>This will fix tabs that should be public but have empty string instead of NULL for user_id. This ensures that users and viewers can see public tabs properly.</p>
<button class="btn btn-info" id="fix-public-tabs-button">Fix Public Tabs</button>
<div id="fix-public-tabs-result" class="mt-3"></div>
<hr class="my-4">
<h5>Clear Varnish Cache</h5>
<p>This will purge all cached content from Varnish Cache. This is useful when you've made changes to images or static content and want to ensure users see the latest version.</p>
<button class="btn btn-warning" id="clear-cache-button">Clear Varnish Cache</button>
<div id="clear-cache-result" class="mt-3"></div>
</div>
</div>
</div>
<?php // endif; ?>
</div>
</div>
</div>
</div>
<?php require_once 'include/footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const updateDbBtn = document.getElementById('update-db-button');
const updateDbResult = document.getElementById('update-db-result');
if (updateDbBtn) {
updateDbBtn.addEventListener('click', async function() {
updateDbBtn.disabled = true;
updateDbResult.innerHTML = `<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div> Checking for updates...`;
try {
const response = await fetch('api/update_database.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (response.ok && data.success) {
let detailsHtml = data.details.map(d => `<li>${d}</li>`).join('');
updateDbResult.innerHTML = `<div class="alert alert-success">
<strong>${data.message}</strong>
<ul>${detailsHtml}</ul>
</div>`;
} else {
throw new Error(data.message || 'An unknown error occurred.');
}
} catch (error) {
updateDbResult.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> ${error.message}</div>`;
} finally {
updateDbBtn.disabled = false;
}
});
}
// --- Handle Fix Orphaned Bookmarks ---
const fixBookmarksBtn = document.getElementById('fix-bookmarks-button');
const fixBookmarksResult = document.getElementById('fix-bookmarks-result');
if (fixBookmarksBtn) {
fixBookmarksBtn.addEventListener('click', async function() {
fixBookmarksBtn.disabled = true;
fixBookmarksResult.innerHTML = `<div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Loading...</span></div> Searching for orphaned bookmarks...`;
try {
const response = await fetch('api/fix_orphaned_bookmarks.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (response.ok && data.success) {
fixBookmarksResult.innerHTML = `<div class="alert alert-success"><strong>${data.message}</strong></div>`;
} else {
throw new Error(data.message || 'An unknown error occurred.');
}
} catch (error) {
fixBookmarksResult.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> ${error.message}</div>`;
} finally {
fixBookmarksBtn.disabled = false;
}
});
}
// --- Handle Fix Public Tabs ---
const fixPublicTabsBtn = document.getElementById('fix-public-tabs-button');
const fixPublicTabsResult = document.getElementById('fix-public-tabs-result');
if (fixPublicTabsBtn) {
fixPublicTabsBtn.addEventListener('click', async function() {
fixPublicTabsBtn.disabled = true;
fixPublicTabsResult.innerHTML = `<div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Loading...</span></div> Checking and fixing public tabs...`;
try {
const response = await fetch('api/fix_public_tabs.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (response.ok && data.success) {
let detailsHtml = '';
if (data.details && data.details.length > 0) {
detailsHtml = '<ul class="mb-0">' + data.details.map(d => `<li>${d}</li>`).join('') + '</ul>';
}
fixPublicTabsResult.innerHTML = `<div class="alert alert-success">
<strong>${data.message}</strong>
${detailsHtml}
</div>`;
} else {
throw new Error(data.message || 'An unknown error occurred.');
}
} catch (error) {
fixPublicTabsResult.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> ${error.message}</div>`;
} finally {
fixPublicTabsBtn.disabled = false;
}
});
}
// --- Handle Clear Varnish Cache ---
const clearCacheBtn = document.getElementById('clear-cache-button');
const clearCacheResult = document.getElementById('clear-cache-result');
if (clearCacheBtn) {
clearCacheBtn.addEventListener('click', async function() {
clearCacheBtn.disabled = true;
clearCacheResult.innerHTML = `<div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Loading...</span></div> Clearing Varnish cache...`;
try {
const response = await fetch('api/purge_cache.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (response.ok && data.success) {
clearCacheResult.innerHTML = `<div class="alert alert-success"><strong>${data.message}</strong></div>`;
} else {
throw new Error(data.message || 'An unknown error occurred.');
}
} catch (error) {
clearCacheResult.innerHTML = `<div class="alert alert-danger"><strong>Error:</strong> ${error.message}</div>`;
} finally {
clearCacheBtn.disabled = false;
}
});
}
// --- New: Handle Backup Import to Tab ---
const importForm = document.getElementById('import-backup-form');
if (importForm) {
importForm.addEventListener('submit', async function(e) {
e.preventDefault();
const submitButton = importForm.querySelector('button[type="submit"]');
const originalButtonText = submitButton.innerHTML;
let resultContainer = document.getElementById('import-result');
if (!resultContainer) {
resultContainer = document.createElement('div');
resultContainer.id = 'import-result';
resultContainer.className = 'mt-3';
importForm.appendChild(resultContainer);
}
submitButton.disabled = true;
submitButton.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Importing...`;
resultContainer.innerHTML = '';
const formData = new FormData(importForm);
try {
const response = await fetch('api/import_backup.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok && data.success) {
resultContainer.innerHTML = `<div class="alert alert-success">${data.message}</div>`;
importForm.reset(); // Clear the form
} else {
const err = new Error(data.message || 'An unknown error occurred during import.');
if (data.error_details) {
err.details = data.error_details;
}
throw err;
}
} catch (error) {
let errorMessage = `<strong>Error:</strong> ${error.message}`;
if (error.details) {
errorMessage += `<br><small class="text-muted"><strong>Details:</strong> ${error.details.message} in <strong>${error.details.file}</strong> on line <strong>${error.details.line}</strong></small>`;
}
resultContainer.innerHTML = `<div class="alert alert-danger">${errorMessage}</div>`;
} finally {
submitButton.disabled = false;
submitButton.innerHTML = originalButtonText;
}
});
}
});
</script>