requireAuth(); // Set headers for JSON response header('Content-Type: application/json'); // Create uploads directory if it doesn't exist $uploadsDir = '../uploads/images'; $thumbsDir = '../uploads/images/thumbs'; if (!file_exists($uploadsDir)) { mkdir($uploadsDir, 0755, true); } if (!file_exists($thumbsDir)) { mkdir($thumbsDir, 0755, true); } // Handle file upload if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['images'])) { $uploadedFiles = []; $errors = []; // Handle multiple files $files = $_FILES['images']; $fileCount = count($files['name']); for ($i = 0; $i < $fileCount; $i++) { if ($files['error'][$i] === UPLOAD_ERR_OK) { $file = [ 'name' => $files['name'][$i], 'type' => $files['type'][$i], 'tmp_name' => $files['tmp_name'][$i], 'error' => $files['error'][$i], 'size' => $files['size'][$i] ]; // Validate file $validation = validateImageFile($file); if ($validation['valid']) { // Generate unique filename $extension = pathinfo($file['name'], PATHINFO_EXTENSION); $filename = uniqid() . '.' . $extension; $filepath = $uploadsDir . '/' . $filename; $thumbFilename = 'thumb_' . $filename; $thumbPath = $thumbsDir . '/' . $thumbFilename; // Move file to uploads directory if (move_uploaded_file($file['tmp_name'], $filepath)) { // Generate thumbnail with fallback $thumbFilename = null; $thumbnailUrl = '/uploads/images/' . $filename; // Fallback to original // Try to generate thumbnail $thumbFilenameTemp = 'thumb_' . $filename; $thumbPathTemp = $thumbsDir . '/' . $thumbFilenameTemp; if (generateThumbnail($filepath, $thumbPathTemp)) { // Thumbnail generation successful $thumbFilename = $thumbFilenameTemp; $thumbnailUrl = '/uploads/images/thumbs/' . $thumbFilenameTemp; } else { // Thumbnail generation failed, log error but continue error_log('Thumbnail generation failed for ' . $file['name'] . ', using original image as fallback'); } // Save to database $imageId = saveImageToDatabase($filename, $file['name'], $file['size'], $thumbFilename); if ($imageId) { $uploadedFiles[] = [ 'id' => $imageId, 'filename' => $filename, 'original_name' => $file['name'], 'size' => $file['size'], 'url' => '/uploads/images/' . $filename, 'thumbnail_url' => $thumbnailUrl ]; } else { $errors[] = 'Failed to save ' . $file['name'] . ' to database'; unlink($filepath); // Remove uploaded file if ($thumbFilename && file_exists($thumbPathTemp)) { unlink($thumbPathTemp); // Remove thumbnail if generated } } } else { $errors[] = 'Failed to upload ' . $file['name']; } } else { $errors[] = $validation['error']; } } else { $errors[] = 'Error uploading file: ' . getUploadErrorMessage($files['error'][$i]); } } echo json_encode([ 'success' => empty($errors), 'files' => $uploadedFiles, 'errors' => $errors ]); exit; } // Handle GET request to fetch existing images if ($_SERVER['REQUEST_METHOD'] === 'GET') { $images = getExistingImages(); echo json_encode([ 'success' => true, 'images' => $images ]); exit; } // Handle DELETE request to remove image if ($_SERVER['REQUEST_METHOD'] === 'DELETE' && isset($_GET['id'])) { $imageId = (int)$_GET['id']; $success = deleteImage($imageId); echo json_encode([ 'success' => $success ]); exit; } echo json_encode(['success' => false, 'error' => 'Invalid request']); /** * Validate uploaded image file */ function validateImageFile($file) { // Check file size (20MB max) $maxSize = 20 * 1024 * 1024; // 20MB if ($file['size'] > $maxSize) { return ['valid' => false, 'error' => 'File too large. Maximum size is 20MB']; } // Check file type $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!in_array($file['type'], $allowedTypes)) { return ['valid' => false, 'error' => 'Invalid file type. Only JPG, PNG, GIF, and WebP are allowed']; } // Check file extension $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($extension, $allowedExtensions)) { return ['valid' => false, 'error' => 'Invalid file extension']; } return ['valid' => true]; } /** * Generate thumbnail for uploaded image */ function generateThumbnail($sourcePath, $destPath, $maxWidth = 300, $maxHeight = 200) { // Check if GD extension is available if (!extension_loaded('gd')) { error_log('GD extension not available for thumbnail generation'); return false; } // Check if source file exists and is readable if (!file_exists($sourcePath) || !is_readable($sourcePath)) { error_log('Source file not found or not readable: ' . $sourcePath); return false; } try { // Get image info $imageInfo = getimagesize($sourcePath); if (!$imageInfo) { error_log('Unable to get image info for: ' . $sourcePath); return false; } $width = $imageInfo[0]; $height = $imageInfo[1]; $type = $imageInfo[2]; error_log("Processing image: {$sourcePath} - Size: {$width}x{$height}, Type: {$type}"); // Calculate new dimensions $ratio = min($maxWidth / $width, $maxHeight / $height); $newWidth = (int)($width * $ratio); $newHeight = (int)($height * $ratio); error_log("New thumbnail size: {$newWidth}x{$newHeight}"); // Create image resource based on type $source = null; switch ($type) { case IMAGETYPE_JPEG: $source = imagecreatefromjpeg($sourcePath); break; case IMAGETYPE_PNG: $source = imagecreatefrompng($sourcePath); break; case IMAGETYPE_GIF: $source = imagecreatefromgif($sourcePath); break; default: error_log("Unsupported image type: {$type}"); return false; } if (!$source) { error_log('Failed to create image resource from: ' . $sourcePath); return false; } // Create new image $thumb = imagecreatetruecolor($newWidth, $newHeight); if (!$thumb) { error_log('Failed to create true color image for thumbnail'); imagedestroy($source); return false; } // Preserve transparency for PNG and GIF if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) { imagealphablending($thumb, false); imagesavealpha($thumb, true); $transparent = imagecolorallocatealpha($thumb, 255, 255, 255, 127); imagefilledrectangle($thumb, 0, 0, $newWidth, $newHeight, $transparent); } // Resize image $resizeResult = imagecopyresampled($thumb, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); if (!$resizeResult) { error_log('Failed to resize image'); imagedestroy($source); imagedestroy($thumb); return false; } // Ensure destination directory exists $destDir = dirname($destPath); if (!is_dir($destDir)) { if (!mkdir($destDir, 0755, true)) { error_log('Failed to create thumbnail directory: ' . $destDir); imagedestroy($source); imagedestroy($thumb); return false; } } // Save thumbnail $result = false; switch ($type) { case IMAGETYPE_JPEG: $result = imagejpeg($thumb, $destPath, 85); break; case IMAGETYPE_PNG: $result = imagepng($thumb, $destPath, 8); break; case IMAGETYPE_GIF: $result = imagegif($thumb, $destPath); break; } // Clean up imagedestroy($source); imagedestroy($thumb); if ($result && file_exists($destPath)) { error_log('Thumbnail successfully created: ' . $destPath); return true; } else { error_log('Failed to save thumbnail: ' . $destPath); return false; } } catch (Exception $e) { error_log('Error generating thumbnail: ' . $e->getMessage()); return false; } } /** * Save image information to database */ function saveImageToDatabase($filename, $originalName, $size, $thumbnailFilename = null) { $db = Database::getInstance(); try { $db->insert('images', [ 'filename' => $filename, 'thumbnail_filename' => $thumbnailFilename, 'original_name' => $originalName, 'file_size' => $size, 'uploaded_at' => date('Y-m-d H:i:s'), 'uploaded_by' => $_SESSION['user_id'] ?? 1 ]); return $db->lastInsertId(); } catch (Exception $e) { error_log('Failed to save image to database: ' . $e->getMessage()); return false; } } /** * Get existing images from database */ function getExistingImages() { $db = Database::getInstance(); try { $images = $db->fetchAll("SELECT * FROM images ORDER BY uploaded_at DESC"); $result = []; foreach ($images as $image) { // Use thumbnail if available, otherwise use original image $thumbnailUrl = $image['thumbnail_filename'] ? '/uploads/images/thumbs/' . $image['thumbnail_filename'] : '/uploads/images/' . $image['filename']; $result[] = [ 'id' => $image['id'], 'filename' => $image['filename'], 'thumbnail_filename' => $image['thumbnail_filename'], 'original_name' => $image['original_name'], 'size' => $image['file_size'], 'url' => '/uploads/images/' . $image['filename'], 'thumbnail_url' => $thumbnailUrl, 'uploaded_at' => $image['uploaded_at'] ]; } return $result; } catch (Exception $e) { error_log('Failed to fetch images: ' . $e->getMessage()); return []; } } /** * Delete image from database and filesystem */ function deleteImage($imageId) { $db = Database::getInstance(); try { // Get image info $image = $db->fetch("SELECT * FROM images WHERE id = ?", [$imageId]); if (!$image) { return false; } // Delete from database $db->delete('images', 'id = ?', [$imageId]); // Delete file from filesystem $filepath = '../uploads/images/' . $image['filename']; if (file_exists($filepath)) { unlink($filepath); } return true; } catch (Exception $e) { error_log('Failed to delete image: ' . $e->getMessage()); return false; } } /** * Get upload error message */ function getUploadErrorMessage($errorCode) { switch ($errorCode) { case UPLOAD_ERR_INI_SIZE: return 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; case UPLOAD_ERR_FORM_SIZE: return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'; case UPLOAD_ERR_PARTIAL: return 'The uploaded file was only partially uploaded'; case UPLOAD_ERR_NO_FILE: return 'No file was uploaded'; case UPLOAD_ERR_NO_TMP_DIR: return 'Missing a temporary folder'; case UPLOAD_ERR_CANT_WRITE: return 'Failed to write file to disk'; case UPLOAD_ERR_EXTENSION: return 'A PHP extension stopped the file upload'; default: return 'Unknown upload error'; } } ?>