feat: complete photo gallery implementation with album management and lightbox viewer
- Added photo gallery carousel view (gallery.php) with all member albums - Implemented album detail view with responsive photo grid and lightbox - Created album creation/editing form with drag-and-drop photo uploads - Added backend processors for album CRUD operations and photo management - Implemented API endpoints for fetching and deleting photos - Added database migration for photo_albums and photos tables - Included comprehensive feature documentation with testing checklist - Updated .htaccess with URL rewrite rules for gallery routes - Added Gallery link to Members Area menu in header - Created upload directory structure (/assets/uploads/gallery/) - Implemented security: CSRF tokens, ownership verification, file validation - Added transaction safety with rollback on errors and cleanup - Features: Lightbox with keyboard navigation, drag-and-drop uploads, responsive design
This commit is contained in:
97
src/processors/delete_album.php
Normal file
97
src/processors/delete_album.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
$rootPath = dirname(dirname(dirname(__DIR__)));
|
||||
require_once($rootPath . '/connection.php');
|
||||
|
||||
$album_id = intval($_GET['id'] ?? 0);
|
||||
|
||||
if (!$album_id) {
|
||||
http_response_code(400);
|
||||
exit('Album ID is required');
|
||||
}
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
// Verify ownership
|
||||
$albumCheck = $conn->prepare("SELECT user_id FROM photo_albums WHERE album_id = ?");
|
||||
$albumCheck->bind_param("i", $album_id);
|
||||
$albumCheck->execute();
|
||||
$albumResult = $albumCheck->get_result();
|
||||
|
||||
if ($albumResult->num_rows === 0) {
|
||||
$conn->close();
|
||||
http_response_code(404);
|
||||
header('Location: gallery');
|
||||
exit;
|
||||
}
|
||||
|
||||
$album = $albumResult->fetch_assoc();
|
||||
if ($album['user_id'] !== $_SESSION['user_id']) {
|
||||
$conn->close();
|
||||
http_response_code(403);
|
||||
header('Location: gallery');
|
||||
exit;
|
||||
}
|
||||
$albumCheck->close();
|
||||
|
||||
try {
|
||||
// Start transaction
|
||||
$conn->begin_transaction();
|
||||
|
||||
// Get all photos for this album
|
||||
$photoStmt = $conn->prepare("SELECT file_path FROM photos WHERE album_id = ?");
|
||||
$photoStmt->bind_param("i", $album_id);
|
||||
$photoStmt->execute();
|
||||
$photoResult = $photoStmt->get_result();
|
||||
|
||||
// Delete photo files
|
||||
while ($photo = $photoResult->fetch_assoc()) {
|
||||
$photoPath = $_SERVER['DOCUMENT_ROOT'] . $photo['file_path'];
|
||||
if (file_exists($photoPath)) {
|
||||
unlink($photoPath);
|
||||
}
|
||||
}
|
||||
$photoStmt->close();
|
||||
|
||||
// Delete photos from database (cascade should handle this)
|
||||
$deletePhotosStmt = $conn->prepare("DELETE FROM photos WHERE album_id = ?");
|
||||
$deletePhotosStmt->bind_param("i", $album_id);
|
||||
$deletePhotosStmt->execute();
|
||||
$deletePhotosStmt->close();
|
||||
|
||||
// Delete album from database
|
||||
$deleteAlbumStmt = $conn->prepare("DELETE FROM photo_albums WHERE album_id = ?");
|
||||
$deleteAlbumStmt->bind_param("i", $album_id);
|
||||
$deleteAlbumStmt->execute();
|
||||
$deleteAlbumStmt->close();
|
||||
|
||||
// Delete album directory
|
||||
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
|
||||
if (is_dir($albumDir)) {
|
||||
rmdir($albumDir);
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
$conn->commit();
|
||||
$conn->close();
|
||||
|
||||
// Redirect to gallery
|
||||
header('Location: gallery');
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Rollback on error
|
||||
$conn->rollback();
|
||||
$conn->close();
|
||||
|
||||
http_response_code(400);
|
||||
echo 'Error deleting album: ' . htmlspecialchars($e->getMessage());
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
115
src/processors/delete_photo.php
Normal file
115
src/processors/delete_photo.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Forbidden']));
|
||||
}
|
||||
|
||||
$rootPath = dirname(dirname(dirname(__DIR__)));
|
||||
require_once($rootPath . '/connection.php');
|
||||
require_once($rootPath . '/functions.php');
|
||||
|
||||
// Validate CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Invalid request']));
|
||||
}
|
||||
|
||||
$photo_id = intval($_POST['photo_id'] ?? 0);
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
if (!$photo_id) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Photo ID is required']));
|
||||
}
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
// Get photo and verify ownership through album
|
||||
$photoStmt = $conn->prepare("
|
||||
SELECT p.photo_id, p.album_id, p.file_path, a.user_id
|
||||
FROM photos p
|
||||
JOIN photo_albums a ON p.album_id = a.album_id
|
||||
WHERE p.photo_id = ?
|
||||
");
|
||||
$photoStmt->bind_param("i", $photo_id);
|
||||
$photoStmt->execute();
|
||||
$photoResult = $photoStmt->get_result();
|
||||
|
||||
if ($photoResult->num_rows === 0) {
|
||||
$conn->close();
|
||||
http_response_code(404);
|
||||
exit(json_encode(['error' => 'Photo not found']));
|
||||
}
|
||||
|
||||
$photo = $photoResult->fetch_assoc();
|
||||
if ($photo['user_id'] !== $user_id) {
|
||||
$conn->close();
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'You do not have permission to delete this photo']));
|
||||
}
|
||||
$photoStmt->close();
|
||||
|
||||
try {
|
||||
// Delete photo from filesystem
|
||||
$photoPath = $_SERVER['DOCUMENT_ROOT'] . $photo['file_path'];
|
||||
if (file_exists($photoPath)) {
|
||||
unlink($photoPath);
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
$deleteStmt = $conn->prepare("DELETE FROM photos WHERE photo_id = ?");
|
||||
$deleteStmt->bind_param("i", $photo_id);
|
||||
$deleteStmt->execute();
|
||||
$deleteStmt->close();
|
||||
|
||||
// Update album's cover image if this was the cover
|
||||
$albumCheck = $conn->prepare("SELECT cover_image FROM photo_albums WHERE album_id = ?");
|
||||
$albumCheck->bind_param("i", $photo['album_id']);
|
||||
$albumCheck->execute();
|
||||
$albumResult = $albumCheck->get_result();
|
||||
$album = $albumResult->fetch_assoc();
|
||||
$albumCheck->close();
|
||||
|
||||
if ($album['cover_image'] === $photo['file_path']) {
|
||||
// Set new cover to first remaining photo
|
||||
$newCoverStmt = $conn->prepare("
|
||||
SELECT file_path FROM photos
|
||||
WHERE album_id = ?
|
||||
ORDER BY display_order ASC
|
||||
LIMIT 1
|
||||
");
|
||||
$newCoverStmt->bind_param("i", $photo['album_id']);
|
||||
$newCoverStmt->execute();
|
||||
$newCoverResult = $newCoverStmt->get_result();
|
||||
|
||||
if ($newCoverResult->num_rows > 0) {
|
||||
$newCover = $newCoverResult->fetch_assoc();
|
||||
$updateCoverStmt = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?");
|
||||
$updateCoverStmt->bind_param("si", $newCover['file_path'], $photo['album_id']);
|
||||
$updateCoverStmt->execute();
|
||||
$updateCoverStmt->close();
|
||||
} else {
|
||||
// No more photos, clear cover image
|
||||
$clearCoverStmt = $conn->prepare("UPDATE photo_albums SET cover_image = NULL WHERE album_id = ?");
|
||||
$clearCoverStmt->bind_param("i", $photo['album_id']);
|
||||
$clearCoverStmt->execute();
|
||||
$clearCoverStmt->close();
|
||||
}
|
||||
$newCoverStmt->close();
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
59
src/processors/get_album_photos.php
Normal file
59
src/processors/get_album_photos.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Unauthorized']));
|
||||
}
|
||||
|
||||
$rootPath = dirname(dirname(dirname(__DIR__)));
|
||||
require_once($rootPath . '/connection.php');
|
||||
|
||||
$album_id = intval($_GET['id'] ?? 0);
|
||||
|
||||
if (!$album_id) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Album ID is required']));
|
||||
}
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
// Verify album exists and user has access
|
||||
$albumCheck = $conn->prepare("SELECT user_id FROM photo_albums WHERE album_id = ?");
|
||||
$albumCheck->bind_param("i", $album_id);
|
||||
$albumCheck->execute();
|
||||
$albumResult = $albumCheck->get_result();
|
||||
|
||||
if ($albumResult->num_rows === 0) {
|
||||
$conn->close();
|
||||
http_response_code(404);
|
||||
exit(json_encode(['error' => 'Album not found']));
|
||||
}
|
||||
|
||||
$album = $albumResult->fetch_assoc();
|
||||
// Allow viewing own albums or public albums (owner is a member)
|
||||
if ($album['user_id'] !== $_SESSION['user_id']) {
|
||||
// For now, only allow owners to edit
|
||||
$conn->close();
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Unauthorized']));
|
||||
}
|
||||
$albumCheck->close();
|
||||
|
||||
// Get photos
|
||||
$photoStmt = $conn->prepare("SELECT photo_id, file_path, caption, display_order FROM photos WHERE album_id = ? ORDER BY display_order ASC");
|
||||
$photoStmt->bind_param("i", $album_id);
|
||||
$photoStmt->execute();
|
||||
$photoResult = $photoStmt->get_result();
|
||||
|
||||
$photos = [];
|
||||
while ($photo = $photoResult->fetch_assoc()) {
|
||||
$photos[] = $photo;
|
||||
}
|
||||
$photoStmt->close();
|
||||
|
||||
$conn->close();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($photos);
|
||||
?>
|
||||
150
src/processors/save_album.php
Normal file
150
src/processors/save_album.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
http_response_code(400);
|
||||
exit('Invalid request');
|
||||
}
|
||||
|
||||
$rootPath = dirname(dirname(dirname(__DIR__)));
|
||||
require_once($rootPath . '/connection.php');
|
||||
require_once($rootPath . '/functions.php');
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
// Validate inputs
|
||||
if (empty($title) || !validateName($title)) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Album title is required and must be valid']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($description) && strlen($description) > 500) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Description must be 500 characters or less']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Create album directory
|
||||
$album_id = null;
|
||||
$cover_image = null;
|
||||
|
||||
try {
|
||||
// Start transaction
|
||||
$conn->begin_transaction();
|
||||
|
||||
// Insert album record
|
||||
$stmt = $conn->prepare("INSERT INTO photo_albums (user_id, title, description, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW())");
|
||||
$stmt->bind_param("iss", $user_id, $title, $description);
|
||||
$stmt->execute();
|
||||
$album_id = $conn->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
// Create album directory
|
||||
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
|
||||
if (!is_dir($albumDir)) {
|
||||
if (!mkdir($albumDir, 0755, true)) {
|
||||
throw new Exception('Failed to create album directory');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle photo uploads
|
||||
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
$maxSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
$displayOrder = 1;
|
||||
$firstPhoto = true;
|
||||
|
||||
for ($i = 0; $i < count($_FILES['photos']['name']); $i++) {
|
||||
if ($_FILES['photos']['error'][$i] !== UPLOAD_ERR_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileName = $_FILES['photos']['name'][$i];
|
||||
$fileTmpName = $_FILES['photos']['tmp_name'][$i];
|
||||
$fileSize = $_FILES['photos']['size'][$i];
|
||||
$fileMime = mime_content_type($fileTmpName);
|
||||
|
||||
// Validate file
|
||||
if (!in_array($fileMime, $allowedMimes)) {
|
||||
throw new Exception('Invalid file type: ' . $fileName);
|
||||
}
|
||||
|
||||
if ($fileSize > $maxSize) {
|
||||
throw new Exception('File too large: ' . $fileName);
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$newFileName = uniqid('photo_') . '.' . $ext;
|
||||
$filePath = $albumDir . '/' . $newFileName;
|
||||
$relativePath = '/assets/uploads/gallery/' . $album_id . '/' . $newFileName;
|
||||
|
||||
if (!move_uploaded_file($fileTmpName, $filePath)) {
|
||||
throw new Exception('Failed to upload: ' . $fileName);
|
||||
}
|
||||
|
||||
// Set first photo as cover
|
||||
if ($firstPhoto) {
|
||||
$updateCover = $conn->prepare("UPDATE photo_albums SET cover_image = ? WHERE album_id = ?");
|
||||
$updateCover->bind_param("si", $relativePath, $album_id);
|
||||
$updateCover->execute();
|
||||
$updateCover->close();
|
||||
$firstPhoto = false;
|
||||
}
|
||||
|
||||
// Insert photo record
|
||||
$caption = $fileName; // Default caption is filename
|
||||
$photoStmt = $conn->prepare("INSERT INTO photos (album_id, file_path, caption, display_order, created_at) VALUES (?, ?, ?, ?, NOW())");
|
||||
$photoStmt->bind_param("issi", $album_id, $relativePath, $caption, $displayOrder);
|
||||
$photoStmt->execute();
|
||||
$photoStmt->close();
|
||||
|
||||
$displayOrder++;
|
||||
}
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
$conn->commit();
|
||||
$conn->close();
|
||||
|
||||
// Redirect to view album
|
||||
header('Location: view_album?id=' . $album_id);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Rollback on error
|
||||
$conn->rollback();
|
||||
$conn->close();
|
||||
|
||||
// Clean up partially uploaded files
|
||||
if ($album_id) {
|
||||
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
|
||||
if (is_dir($albumDir)) {
|
||||
array_map('unlink', glob($albumDir . '/*'));
|
||||
rmdir($albumDir);
|
||||
}
|
||||
// Delete album record (will cascade delete photos)
|
||||
$cleanupConn = openDatabaseConnection();
|
||||
$cleanupConn->query("DELETE FROM photo_albums WHERE album_id = " . intval($album_id));
|
||||
$cleanupConn->close();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
153
src/processors/update_album.php
Normal file
153
src/processors/update_album.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
// Validate CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
|
||||
http_response_code(400);
|
||||
exit('Invalid request');
|
||||
}
|
||||
|
||||
$rootPath = dirname(dirname(dirname(__DIR__)));
|
||||
require_once($rootPath . '/connection.php');
|
||||
require_once($rootPath . '/functions.php');
|
||||
|
||||
$conn = openDatabaseConnection();
|
||||
|
||||
$album_id = intval($_POST['album_id'] ?? 0);
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
if (!$album_id) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Album ID is required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
$ownerCheck = $conn->prepare("SELECT user_id FROM photo_albums WHERE album_id = ?");
|
||||
$ownerCheck->bind_param("i", $album_id);
|
||||
$ownerCheck->execute();
|
||||
$ownerResult = $ownerCheck->get_result();
|
||||
|
||||
if ($ownerResult->num_rows === 0) {
|
||||
$conn->close();
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Album not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$owner = $ownerResult->fetch_assoc();
|
||||
if ($owner['user_id'] !== $user_id) {
|
||||
$conn->close();
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'You do not have permission to edit this album']);
|
||||
exit;
|
||||
}
|
||||
$ownerCheck->close();
|
||||
|
||||
// Validate inputs
|
||||
if (empty($title) || !validateName($title)) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Album title is required and must be valid']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($description) && strlen($description) > 500) {
|
||||
$conn->close();
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Description must be 500 characters or less']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Start transaction
|
||||
$conn->begin_transaction();
|
||||
|
||||
// Update album
|
||||
$updateStmt = $conn->prepare("UPDATE photo_albums SET title = ?, description = ?, updated_at = NOW() WHERE album_id = ?");
|
||||
$updateStmt->bind_param("ssi", $title, $description, $album_id);
|
||||
$updateStmt->execute();
|
||||
$updateStmt->close();
|
||||
|
||||
// Handle photo uploads if any
|
||||
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
$maxSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
|
||||
|
||||
// Get current max display order
|
||||
$orderStmt = $conn->prepare("SELECT MAX(display_order) as max_order FROM photos WHERE album_id = ?");
|
||||
$orderStmt->bind_param("i", $album_id);
|
||||
$orderStmt->execute();
|
||||
$orderResult = $orderStmt->get_result();
|
||||
$orderRow = $orderResult->fetch_assoc();
|
||||
$displayOrder = ($orderRow['max_order'] ?? 0) + 1;
|
||||
$orderStmt->close();
|
||||
|
||||
for ($i = 0; $i < count($_FILES['photos']['name']); $i++) {
|
||||
if ($_FILES['photos']['error'][$i] !== UPLOAD_ERR_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileName = $_FILES['photos']['name'][$i];
|
||||
$fileTmpName = $_FILES['photos']['tmp_name'][$i];
|
||||
$fileSize = $_FILES['photos']['size'][$i];
|
||||
$fileMime = mime_content_type($fileTmpName);
|
||||
|
||||
// Validate file
|
||||
if (!in_array($fileMime, $allowedMimes)) {
|
||||
throw new Exception('Invalid file type: ' . $fileName);
|
||||
}
|
||||
|
||||
if ($fileSize > $maxSize) {
|
||||
throw new Exception('File too large: ' . $fileName);
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$newFileName = uniqid('photo_') . '.' . $ext;
|
||||
$filePath = $albumDir . '/' . $newFileName;
|
||||
$relativePath = '/assets/uploads/gallery/' . $album_id . '/' . $newFileName;
|
||||
|
||||
if (!move_uploaded_file($fileTmpName, $filePath)) {
|
||||
throw new Exception('Failed to upload: ' . $fileName);
|
||||
}
|
||||
|
||||
// Insert photo record
|
||||
$caption = $fileName; // Default caption is filename
|
||||
$photoStmt = $conn->prepare("INSERT INTO photos (album_id, file_path, caption, display_order, created_at) VALUES (?, ?, ?, ?, NOW())");
|
||||
$photoStmt->bind_param("issi", $album_id, $relativePath, $caption, $displayOrder);
|
||||
$photoStmt->execute();
|
||||
$photoStmt->close();
|
||||
|
||||
$displayOrder++;
|
||||
}
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
$conn->commit();
|
||||
$conn->close();
|
||||
|
||||
// Redirect back to album view
|
||||
header('Location: view_album?id=' . $album_id);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Rollback on error
|
||||
$conn->rollback();
|
||||
$conn->close();
|
||||
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user