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

>
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.



Fix Public Tabs Visibility

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.