Fix photo upload on mobile devices
- Add HEIC/HEIF support for iPhone camera photos - Make frontend file type validation more permissive for mobile browsers - Add 'capture' attribute to prefer rear camera on mobile - Include actual file extension in error messages for debugging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
12a8c054f3
commit
9fc984bea3
3 changed files with 34 additions and 20 deletions
|
|
@ -64,14 +64,19 @@ if (bizId LTE 0) {
|
|||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- Validate file type --->
|
||||
<cfset allowedExtensions = "jpg,jpeg,gif,png,webp">
|
||||
<!--- Validate file type (include HEIC for iPhone) --->
|
||||
<cfset allowedExtensions = "jpg,jpeg,gif,png,webp,heic,heif">
|
||||
<cfif NOT listFindNoCase(allowedExtensions, actualExt)>
|
||||
<cffile action="DELETE" file="#headersDir#/#uploadResult.ServerFile#">
|
||||
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "invalid_type", "MESSAGE": "Only image files are accepted (jpg, jpeg, gif, png, webp)" })#</cfoutput>
|
||||
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "invalid_type", "MESSAGE": "Only image files are accepted (jpg, jpeg, gif, png, webp, heic)" })#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<!--- Convert HEIC/HEIF extension to jpg for consistency --->
|
||||
<cfif actualExt EQ "heic" OR actualExt EQ "heif">
|
||||
<cfset actualExt = "jpg">
|
||||
</cfif>
|
||||
|
||||
<!--- No resize - accept image as-is --->
|
||||
|
||||
<!--- Delete old header if exists --->
|
||||
|
|
|
|||
|
|
@ -26,17 +26,23 @@ if (itemId LTE 0) {
|
|||
<!--- Upload the file to temp location first --->
|
||||
<cffile action="UPLOAD" filefield="photo" destination="#itemsDir#/" nameconflict="MAKEUNIQUE" mode="755" result="uploadResult">
|
||||
|
||||
<!--- Validate file type --->
|
||||
<cfset allowedExtensions = "jpg,jpeg,gif,png,webp">
|
||||
<cfif NOT listFindNoCase(allowedExtensions, uploadResult.ClientFileExt)>
|
||||
<!--- 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)" })#</cfoutput>
|
||||
<cfoutput>#serializeJSON({ "OK": false, "ERROR": "invalid_type", "MESSAGE": "Only image files are accepted (jpg, jpeg, gif, png, webp, heic). Got: #actualExt#" })#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<!--- Convert HEIC/HEIF to jpg for broader compatibility --->
|
||||
<cfif actualExt EQ "heic" OR actualExt EQ "heif">
|
||||
<cfset actualExt = "jpg">
|
||||
</cfif>
|
||||
|
||||
<!--- Delete old photo if exists (any extension) --->
|
||||
<cfscript>
|
||||
for (ext in listToArray(allowedExtensions)) {
|
||||
for (ext in listToArray("jpg,jpeg,gif,png,webp,heic,heif")) {
|
||||
oldFile = "#itemsDir#/#itemId#.#ext#";
|
||||
if (fileExists(oldFile)) {
|
||||
try { fileDelete(oldFile); } catch (any e) {}
|
||||
|
|
@ -44,15 +50,15 @@ for (ext in listToArray(allowedExtensions)) {
|
|||
}
|
||||
</cfscript>
|
||||
|
||||
<!--- Rename to ItemID.ext --->
|
||||
<cffile action="RENAME" source="#itemsDir#/#uploadResult.ServerFile#" destination="#itemsDir#/#itemId#.#uploadResult.ClientFileExt#" mode="755">
|
||||
<!--- Rename to ItemID.ext (use actualExt which may be converted from heic) --->
|
||||
<cffile action="RENAME" source="#itemsDir#/#uploadResult.ServerFile#" destination="#itemsDir#/#itemId#.#actualExt#" mode="755">
|
||||
|
||||
<!--- Return success with image URL --->
|
||||
<cfoutput>#serializeJSON({
|
||||
"OK": true,
|
||||
"ERROR": "",
|
||||
"MESSAGE": "Photo uploaded successfully",
|
||||
"IMAGEURL": "/uploads/items/#itemId#.#uploadResult.ClientFileExt#"
|
||||
"IMAGEURL": "/uploads/items/#itemId#.#actualExt#"
|
||||
})#</cfoutput>
|
||||
|
||||
<cfcatch type="any">
|
||||
|
|
|
|||
|
|
@ -2819,14 +2819,17 @@
|
|||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/jpeg,image/png,image/gif,image/webp';
|
||||
input.accept = 'image/jpeg,image/png,image/gif,image/webp,image/heic,image/heif,.heic,.heif';
|
||||
input.capture = 'environment'; // Prefer rear camera on mobile
|
||||
input.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!validTypes.includes(file.type)) {
|
||||
this.toast('Please select a valid image (JPG, PNG, GIF, or WebP)', 'error');
|
||||
// Be permissive with file types - server will validate
|
||||
// Mobile cameras may report inconsistent MIME types
|
||||
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/heic', 'image/heif', ''];
|
||||
if (file.type && !validTypes.includes(file.type) && !file.type.startsWith('image/')) {
|
||||
this.toast('Please select a valid image file', 'error');
|
||||
return;
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
|
|
@ -3115,15 +3118,15 @@
|
|||
uploadHeader() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/jpeg,image/png,image/gif,image/webp';
|
||||
input.accept = 'image/jpeg,image/png,image/gif,image/webp,image/heic,image/heif,.heic,.heif';
|
||||
input.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file type
|
||||
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!validTypes.includes(file.type)) {
|
||||
this.toast('Please select a valid image (JPG, PNG, GIF, or WebP)', 'error');
|
||||
// Validate file type (be permissive for mobile cameras)
|
||||
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/heic', 'image/heif', ''];
|
||||
if (file.type && !validTypes.includes(file.type) && !file.type.startsWith('image/')) {
|
||||
this.toast('Please select a valid image file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue