638 lines
42 KiB
PHP
638 lines
42 KiB
PHP
<?php
|
|
$page_title = 'Settings - Bookmarkly';
|
|
|
|
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'];
|
|
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">
|
|
<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 if (isAdmin()): ?>
|
|
<div class="tab-pane fade" id="v-pills-system" role="tabpanel">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4>System Maintenance</h4>
|
|
</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>
|
|
</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;
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- 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>
|