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'; ?>

>
Current logo: Current Logo
Current favicon: Current Favicon

>
>

These settings serve as the global default. They can be overridden by individual tabs in the Tab Management page.


Current Background

>




System Maintenance

Update Database Schema

If new features require database changes, you can apply them here. This process is safe to run multiple times.