This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/menu/uploadItemPhoto.cfm
John Mizerek a318b8668f Fix cart/tax issues and add menu item thumbnails
- uploadItemPhoto: Add EXIF orientation fix, generate thumb/medium/full sizes
- getActiveCart: Disable old cart lookup (always returns no cart)
- getOrCreateCart: Always create fresh cart instead of reusing old ones
- getCart: Add IsDeleted filter, calculate subtotal/tax/total server-side
- getDetail: Remove default 8.25% tax rate (business must configure)
- menu-builder: Add lightbox for full-size images, use thumbnail URLs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 14:22:54 -08:00

158 lines
5 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cftry>
<cfset itemsDir = expandPath("/uploads/items")>
<cfscript>
// Get ItemID from form
itemId = 0;
if (structKeyExists(form, "ItemID") && isNumeric(form.ItemID) && form.ItemID GT 0) {
itemId = int(form.ItemID);
}
if (itemId LTE 0) {
apiAbort({ "OK": false, "ERROR": "missing_itemid", "MESSAGE": "ItemID is required" });
}
// Fix EXIF orientation - rotate image based on EXIF Orientation tag
function fixOrientation(img) {
try {
exif = imageGetEXIFMetadata(img);
if (structKeyExists(exif, "Orientation")) {
orientation = val(exif.Orientation);
switch(orientation) {
case 3: imageRotate(img, 180); break;
case 6: imageRotate(img, 90); break;
case 8: imageRotate(img, 270); break;
// 2,4,5,7 involve flips which are rare, skip for now
}
}
} catch (any e) {
// No EXIF or can't read it - that's fine
}
return img;
}
// Resize image maintaining aspect ratio, fitting within maxSize box
function resizeToFit(img, maxSize) {
w = imageGetWidth(img);
h = imageGetHeight(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));
}
imageResize(img, newW, newH, "highQuality");
return img;
}
// Create square thumbnail (center crop)
function createSquareThumb(img, size) {
thumb = imageCopy(img, 0, 0, imageGetWidth(img), imageGetHeight(img));
w = imageGetWidth(thumb);
h = imageGetHeight(thumb);
// Resize so smallest dimension equals size
if (w > h) {
imageResize(thumb, "", size, "highQuality");
} else {
imageResize(thumb, size, "", "highQuality");
}
// Center crop to square
w = imageGetWidth(thumb);
h = imageGetHeight(thumb);
if (w > h) {
x = int((w - h) / 2);
imageCrop(thumb, x, 0, h, h);
} else if (h > w) {
y = int((h - w) / 2);
imageCrop(thumb, 0, y, w, w);
}
// Final resize to exact size
imageResize(thumb, size, size, "highQuality");
return thumb;
}
</cfscript>
<!--- Check if file was uploaded --->
<cfif NOT structKeyExists(form, "photo") OR form.photo EQ "">
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "no_file", "MESSAGE": "No file was uploaded" })#</cfoutput>
<cfabort>
</cfif>
<!--- Upload the file to temp location first --->
<cffile action="UPLOAD" filefield="photo" destination="#itemsDir#/" nameconflict="MAKEUNIQUE" mode="755" result="uploadResult">
<!--- Validate file type (include HEIC/HEIF for iPhone) --->
<cfset allowedExtensions = "jpg,jpeg,gif,png,webp,heic,heif">
<cfset actualExt = lCase(uploadResult.ClientFileExt)>
<cfif NOT listFindNoCase(allowedExtensions, actualExt)>
<cffile action="DELETE" file="#itemsDir#/#uploadResult.ServerFile#">
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "invalid_type", "MESSAGE": "Only image files are accepted (jpg, jpeg, gif, png, webp, heic). Got: #actualExt#" })#</cfoutput>
<cfabort>
</cfif>
<!--- Delete old photos and thumbnails if they exist --->
<cfscript>
for (ext in listToArray("jpg,jpeg,gif,png,webp")) {
for (suffix in ["", "_thumb", "_medium"]) {
oldFile = "#itemsDir#/#itemId##suffix#.#ext#";
if (fileExists(oldFile)) {
try { fileDelete(oldFile); } catch (any e) {}
}
}
}
</cfscript>
<!--- Read the uploaded image and process --->
<cfscript>
uploadedFile = "#itemsDir#/#uploadResult.ServerFile#";
img = imageNew(uploadedFile);
// Fix EXIF orientation (portrait photos appearing landscape)
img = fixOrientation(img);
// Create thumbnail (64x64 square for list view - 2x for retina)
thumb = createSquareThumb(img, 128);
imageWrite(thumb, "#itemsDir#/#itemId#_thumb.jpg", 0.85);
// Create medium size (400px max for detail view)
medium = imageCopy(img, 0, 0, imageGetWidth(img), imageGetHeight(img));
medium = resizeToFit(medium, 400);
imageWrite(medium, "#itemsDir#/#itemId#_medium.jpg", 0.85);
// Save full size (max 1200px to keep file sizes reasonable)
img = resizeToFit(img, 1200);
imageWrite(img, "#itemsDir#/#itemId#.jpg", 0.90);
// Delete the original uploaded file
try { fileDelete(uploadedFile); } catch (any e) {}
// Add cache buster
cacheBuster = dateFormat(now(), "yyyymmdd") & timeFormat(now(), "HHmmss");
</cfscript>
<!--- Return success with image URLs --->
<cfoutput>#serializeJSON({
"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#"
})#</cfoutput>
<cfcatch type="any">
<cfheader statuscode="200" statustext="OK">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "server_error", "MESSAGE": cfcatch.message, "DETAIL": cfcatch.detail })#</cfoutput>
</cfcatch>
</cftry>