prepare("SELECT icon FROM bookmarks WHERE id = ?"); $stmt->execute([$bookmark_id]); $old_icon = $stmt->fetchColumn(); // Check if old icon exists, is custom, and is not the same path as the new one if ($old_icon && file_exists($old_icon) && strpos($old_icon, 'data/images/custom/') === 0 && $old_icon !== $destination) { unlink($old_icon); } } if (move_uploaded_file($_FILES['icon_upload']['tmp_name'], $destination)) { return $destination; // Return the path to be saved in DB } } } return null; // No upload or failed upload } // Handle POST requests for CRUD operations if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Check edit permissions for the submitted tab_id for all actions $submitted_tab_id = $_POST['tab_id'] ?? $_GET['tab_id'] ?? null; if (isset($_POST['action']) && !canEditTab($submitted_tab_id)) { $_SESSION['flash_message'] = ['text' => 'You do not have permission to perform this action.', 'type' => 'danger']; header('Location: bookmarks.php?tab_id=' . $submitted_tab_id); exit; } $action = $_POST['action'] ?? ''; // --- Category Actions --- if ($action === 'add_category') { $category_name = trim($_POST['category_name'] ?? ''); $tab_id = filter_input(INPUT_POST, 'tab_id', FILTER_VALIDATE_INT); if (!empty($category_name) && $tab_id) { $sort_stmt = $pdo->prepare("SELECT MAX(sort_order) as max_sort FROM categories WHERE tab_id = ?"); $sort_stmt->execute([$tab_id]); $max_sort = $sort_stmt->fetchColumn() ?: 0; $stmt = $pdo->prepare("INSERT INTO categories (name, sort_order, tab_id) VALUES (?, ?, ?)"); if ($stmt->execute([$category_name, $max_sort + 1, $tab_id])) { $_SESSION['flash_message'] = ['text' => 'Category added successfully.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error adding category.', 'type' => 'danger']; } } else { $_SESSION['flash_message'] = ['text' => 'Category name and tab ID are required.', 'type' => 'warning']; } } elseif ($action === 'delete_category') { $category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT); if ($category_id) { $stmt = $pdo->prepare("DELETE FROM categories WHERE id = ?"); if ($stmt->execute([$category_id])) { $_SESSION['flash_message'] = ['text' => 'Category and its bookmarks have been deleted.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error deleting category.', 'type' => 'danger']; } } } elseif ($action === 'edit_category') { $category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT); $category_name = trim($_POST['category_name'] ?? ''); if ($category_id && !empty($category_name)) { $stmt = $pdo->prepare("UPDATE categories SET name = ? WHERE id = ?"); if ($stmt->execute([$category_name, $category_id])) { $_SESSION['flash_message'] = ['text' => 'Category renamed successfully.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error renaming category.', 'type' => 'danger']; } } else { $_SESSION['flash_message'] = ['text' => 'Invalid data for renaming category.', 'type' => 'warning']; } } // --- Bookmark Actions --- elseif ($action === 'add_bookmark') { $title = trim($_POST['title'] ?? ''); $url = trim($_POST['url'] ?? ''); if (!empty($url) && !preg_match('/^https?:\/\//i', $url)) { $url = 'https://' . $url; } $category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT); $icon_url = trim($_POST['icon_url'] ?? ''); $is_favorite = isset($_POST['is_favorite']) ? 1 : 0; $icon = $icon_url; // Default to URL $new_icon_path = handle_icon_upload(); if ($new_icon_path) { $icon = $new_icon_path; } if ($title && $url && $category_id) { $sort_stmt = $pdo->prepare("SELECT MAX(sort_order) as max_sort FROM bookmarks WHERE category_id = ?"); $sort_stmt->execute([$category_id]); $max_sort = $sort_stmt->fetchColumn() ?: 0; $stmt = $pdo->prepare( "INSERT INTO bookmarks (category_id, title, url, icon, is_favorite, sort_order) VALUES (?, ?, ?, ?, ?, ?)" ); if ($stmt->execute([$category_id, $title, $url, $icon, $is_favorite, $max_sort + 1])) { $bookmark_id = $pdo->lastInsertId(); // Get the ID of the new bookmark // Handle icon upload after getting the ID $new_icon_path = handle_icon_upload($bookmark_id, $title); if ($new_icon_path) { // Update the bookmark with the new icon path $update_stmt = $pdo->prepare("UPDATE bookmarks SET icon = ? WHERE id = ?"); $update_stmt->execute([$new_icon_path, $bookmark_id]); } $_SESSION['flash_message'] = ['text' => 'Bookmark added successfully.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error adding bookmark.', 'type' => 'danger']; } } else { $_SESSION['flash_message'] = ['text' => 'Title, URL, and category are required.', 'type' => 'warning']; } } elseif ($action === 'delete_bookmark') { $bookmark_id = filter_input(INPUT_POST, 'bookmark_id', FILTER_VALIDATE_INT); if ($bookmark_id) { $stmt = $pdo->prepare("DELETE FROM bookmarks WHERE id = ?"); if ($stmt->execute([$bookmark_id])) { $_SESSION['flash_message'] = ['text' => 'Bookmark deleted.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error deleting bookmark.', 'type' => 'danger']; } } } elseif ($action === 'edit_bookmark') { $bookmark_id = filter_input(INPUT_POST, 'bookmark_id', FILTER_VALIDATE_INT); $title = trim($_POST['title'] ?? ''); $url = trim($_POST['url'] ?? ''); if (!empty($url) && !preg_match('/^https?:\/\//i', $url)) { $url = 'https://' . $url; } $category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT); $icon_url = trim($_POST['icon_url'] ?? ''); $is_favorite = isset($_POST['is_favorite']) ? 1 : 0; $remove_icon = ($_POST['remove_icon'] ?? '0') === '1'; if ($bookmark_id && $title && $url && $category_id) { // Fetch current icon to manage file deletion $stmt = $pdo->prepare("SELECT icon FROM bookmarks WHERE id = ?"); $stmt->execute([$bookmark_id]); $current_icon = $stmt->fetchColumn(); $icon = $current_icon; // Keep old icon by default $new_icon_path = handle_icon_upload($bookmark_id, $title); if ($new_icon_path) { // 1. New upload takes highest precedence $icon = $new_icon_path; } else if ($remove_icon) { // 2. Remove icon takes next precedence if ($current_icon && file_exists($current_icon) && strpos($current_icon, 'data/images/custom/') === 0) { unlink($current_icon); } // After removing, use whatever is in the URL field (might be empty) $icon = $icon_url; } else { // 3. No upload, no removal. Handle URL input field. // If the current icon is NOT a custom file, we can just save the icon_url. if (strpos($current_icon ?? '', 'data/images/custom/') !== 0) { $icon = $icon_url; } // If it IS a custom file, we should only change it if the user provided a new icon_url. else if (!empty($icon_url)) { // User is switching from custom to URL. Delete the old file. if (file_exists($current_icon)) { unlink($current_icon); } $icon = $icon_url; } // Implicit else: current icon is custom, icon_url is empty. Do nothing to keep the custom icon. } $stmt = $pdo->prepare( "UPDATE bookmarks SET title = ?, url = ?, category_id = ?, icon = ?, is_favorite = ? WHERE id = ?" ); if ($stmt->execute([$title, $url, $category_id, $icon, $is_favorite, $bookmark_id])) { $_SESSION['flash_message'] = ['text' => 'Bookmark updated successfully.', 'type' => 'success']; } else { $_SESSION['flash_message'] = ['text' => 'Error updating bookmark.', 'type' => 'danger']; } } else { $_SESSION['flash_message'] = ['text' => 'Invalid data for updating bookmark.', 'type' => 'warning']; } } // Redirect to the same page to prevent form resubmission, preserving the tab $tab_id = $_POST['tab_id'] ?? $_GET['tab_id'] ?? 1; header('Location: bookmarks.php?tab_id=' . $tab_id); exit; } // Fetch visible tabs for the user $current_user_id = $_SESSION['user_id'] ?? null; $current_user_role = $_SESSION['role'] ?? null; $tabs_stmt_sql = "SELECT DISTINCT t.* FROM tabs t "; $params = []; if ($current_user_id) { if ($current_user_role === 'admin') { // Admin sees all tabs that are not explicitly hidden for them $tabs_stmt_sql .= " LEFT JOIN user_tab_visibility utv ON t.id = utv.tab_id AND utv.user_id = :current_user_id WHERE utv.user_id IS NULL "; } else { // 'user' and 'viewer' roles // Users/Viewers see public tabs, their own tabs, or tabs they have access to, // as long as they are not explicitly hidden. $tabs_stmt_sql .= " LEFT JOIN tab_viewer_access tva ON t.id = tva.tab_id LEFT JOIN user_tab_visibility utv ON t.id = utv.tab_id AND utv.user_id = :current_user_id WHERE utv.user_id IS NULL AND (t.user_id IS NULL OR t.user_id = :current_user_id OR tva.user_id = :current_user_id) "; } $params[':current_user_id'] = $current_user_id; } else { // Guest sees only public tabs $tabs_stmt_sql .= "WHERE t.user_id IS NULL"; } $tabs_stmt_sql .= " ORDER BY t.id"; $tabs_stmt = $pdo->prepare($tabs_stmt_sql); $tabs_stmt->execute($params); $tabs = $tabs_stmt->fetchAll(); // Fetch all categories and their bookmarks for display for the current tab $current_tab_id = $_GET['tab_id'] ?? ($tabs[0]['id'] ?? 1); // Check if the current user can edit the currently viewed tab $can_edit = canEditTab($current_tab_id); $stmt = $pdo->prepare(" SELECT c.id as category_id, c.name as category_name, c.sort_order as category_sort_order, b.id as bookmark_id, b.title as bookmark_title, b.url as bookmark_url, b.icon as bookmark_icon, b.is_favorite as bookmark_is_favorite, b.sort_order as bookmark_sort_order FROM categories c LEFT JOIN bookmarks b ON c.id = b.category_id WHERE c.tab_id = :tab_id ORDER BY c.sort_order, c.name, b.sort_order, b.title "); $stmt->execute(['tab_id' => $current_tab_id]); $results = $stmt->fetchAll(); $categories = []; foreach ($results as $row) { if (!isset($categories[$row['category_id']])) { $categories[$row['category_id']] = [ 'id' => $row['category_id'], 'name' => $row['category_name'], 'bookmarks' => [] ]; } if ($row['bookmark_id']) { $categories[$row['category_id']]['bookmarks'][] = [ 'id' => $row['bookmark_id'], 'title' => $row['bookmark_title'], 'url' => $row['bookmark_url'], 'icon' => $row['bookmark_icon'], 'is_favorite' => $row['bookmark_is_favorite'] ]; } } require_once 'include/header.php'; ?>