164 lines
5.1 KiB
Text
164 lines
5.1 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);
|
|
thumbPath = "#itemsDir#/#itemId#_thumb.jpg";
|
|
imageWrite(thumb, thumbPath, 0.85);
|
|
fileSetAccessMode(thumbPath, "644");
|
|
|
|
// Create medium size (400px max for detail view)
|
|
medium = imageCopy(img, 0, 0, imageGetWidth(img), imageGetHeight(img));
|
|
medium = resizeToFit(medium, 400);
|
|
mediumPath = "#itemsDir#/#itemId#_medium.jpg";
|
|
imageWrite(medium, mediumPath, 0.85);
|
|
fileSetAccessMode(mediumPath, "644");
|
|
|
|
// Save full size (max 1200px to keep file sizes reasonable)
|
|
img = resizeToFit(img, 1200);
|
|
fullPath = "#itemsDir#/#itemId#.jpg";
|
|
imageWrite(img, fullPath, 0.90);
|
|
fileSetAccessMode(fullPath, "644");
|
|
|
|
// 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>
|