payfrit-api/api/menu/uploadItemPhoto.php
John Mizerek 28d86ba6e5 Fix production webroot path — both servers use /opt/lucee/tomcat/webapps/ROOT
Added luceeWebroot() helper to avoid repeating the path. The previous
fix incorrectly used /var/www/biz.payfrit.com for production, but both
dev and biz use the same Lucee webroot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:19:10 -07:00

152 lines
5.2 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
runAuth();
/**
* Upload Item Photo
*
* Multipart form: ItemID (int), photo (file)
* Creates thumbnail (128px square), medium (400px), and full (1200px max) versions.
*/
$itemId = (int) ($_POST['ItemID'] ?? 0);
if ($itemId <= 0) {
apiAbort(['OK' => false, 'ERROR' => 'missing_itemid', 'MESSAGE' => 'ItemID is required']);
}
if (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
jsonResponse(['OK' => false, 'ERROR' => 'no_file', 'MESSAGE' => 'No file was uploaded']);
}
$allowedExtensions = ['jpg', 'jpeg', 'gif', 'png', 'webp', 'heic', 'heif'];
$ext = strtolower(pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExtensions)) {
jsonResponse(['OK' => false, 'ERROR' => 'invalid_type', 'MESSAGE' => "Only image files are accepted (jpg, jpeg, gif, png, webp, heic). Got: $ext"]);
}
// Determine uploads directory (must be in Lucee webroot, not PHP docroot)
$itemsDir = luceeWebroot() . '/uploads/items';
if (!is_dir($itemsDir)) {
mkdir($itemsDir, 0755, true);
}
try {
$tmpFile = $_FILES['photo']['tmp_name'];
// Delete old photos and thumbnails
foreach (['jpg', 'jpeg', 'gif', 'png', 'webp'] as $oldExt) {
foreach (['', '_thumb', '_medium'] as $suffix) {
$oldFile = "$itemsDir/{$itemId}{$suffix}.{$oldExt}";
if (file_exists($oldFile)) {
@unlink($oldFile);
}
}
}
// Load image with GD
$srcImage = null;
$mime = mime_content_type($tmpFile);
switch ($mime) {
case 'image/jpeg': $srcImage = imagecreatefromjpeg($tmpFile); break;
case 'image/png': $srcImage = imagecreatefrompng($tmpFile); break;
case 'image/gif': $srcImage = imagecreatefromgif($tmpFile); break;
case 'image/webp': $srcImage = imagecreatefromwebp($tmpFile); break;
default:
// Try JPEG as fallback (HEIC converted by server)
$srcImage = @imagecreatefromjpeg($tmpFile);
}
if (!$srcImage) {
jsonResponse(['OK' => false, 'ERROR' => 'invalid_image', 'MESSAGE' => 'Could not process image file']);
}
// Fix EXIF orientation for JPEG
if (function_exists('exif_read_data') && in_array($mime, ['image/jpeg', 'image/tiff'])) {
$exif = @exif_read_data($tmpFile);
if ($exif && isset($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 3: $srcImage = imagerotate($srcImage, 180, 0); break;
case 6: $srcImage = imagerotate($srcImage, -90, 0); break;
case 8: $srcImage = imagerotate($srcImage, 90, 0); break;
}
}
}
$origW = imagesx($srcImage);
$origH = imagesy($srcImage);
// Helper: resize to fit within maxSize box
$resizeToFit = function($img, int $maxSize) {
$w = imagesx($img);
$h = imagesy($img);
if ($w <= $maxSize && $h <= $maxSize) return $img;
if ($w > $h) {
$newW = $maxSize;
$newH = (int) ($h * ($maxSize / $w));
} else {
$newH = $maxSize;
$newW = (int) ($w * ($maxSize / $h));
}
$resized = imagecreatetruecolor($newW, $newH);
imagecopyresampled($resized, $img, 0, 0, 0, 0, $newW, $newH, $w, $h);
return $resized;
};
// Helper: create square center-crop thumbnail
$createSquareThumb = function($img, int $size) {
$w = imagesx($img);
$h = imagesy($img);
// Resize so smallest dimension equals size
if ($w > $h) {
$newH = $size;
$newW = (int) ($w * ($size / $h));
} else {
$newW = $size;
$newH = (int) ($h * ($size / $w));
}
$resized = imagecreatetruecolor($newW, $newH);
imagecopyresampled($resized, $img, 0, 0, 0, 0, $newW, $newH, $w, $h);
// Center crop to square
$x = (int) (($newW - $size) / 2);
$y = (int) (($newH - $size) / 2);
$thumb = imagecreatetruecolor($size, $size);
imagecopy($thumb, $resized, 0, 0, $x, $y, $size, $size);
imagedestroy($resized);
return $thumb;
};
// Create thumbnail (128x128 square for retina)
$thumb = $createSquareThumb($srcImage, 128);
imagejpeg($thumb, "$itemsDir/{$itemId}_thumb.jpg", 85);
imagedestroy($thumb);
// Create medium (400px max)
$medium = $resizeToFit($srcImage, 400);
imagejpeg($medium, "$itemsDir/{$itemId}_medium.jpg", 85);
if ($medium !== $srcImage) imagedestroy($medium);
// Save full size (1200px max)
$full = $resizeToFit($srcImage, 1200);
imagejpeg($full, "$itemsDir/{$itemId}.jpg", 90);
if ($full !== $srcImage) imagedestroy($full);
imagedestroy($srcImage);
$cacheBuster = date('YmdHis');
jsonResponse([
'OK' => true,
'ERROR' => '',
'MESSAGE' => 'Photo uploaded successfully',
'IMAGEURL' => "/uploads/items/{$itemId}.jpg?v={$cacheBuster}",
'THUMBURL' => "/uploads/items/{$itemId}_thumb.jpg?v={$cacheBuster}",
'MEDIUMURL' => "/uploads/items/{$itemId}_medium.jpg?v={$cacheBuster}",
]);
} catch (Exception $e) {
jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage(), 'DETAIL' => '']);
}