$(document).ready(function () { setupStep1Validation("#missingInfo"); var curretnCulture = $("#currentLanguage").val(); $('#failUploadRequestMoreInfo').css('display', 'none'); $("#viewRequestedDetails").on('click', function (e) { e.preventDefault(); var reqNum = $("#requestNum").val().trim(); var assmntNum = $("#assessmentNum").val().trim(); var reqNumRegex = /^RC-\d{7}$/; var assmntNumRegex = /^DA\d{7}$/; if (reqNum == "" || assmntNum == "") { $("#errorMessageCommon").css('display', 'block'); $("#errorMessageCommon").text(fieldsRequired); } //else if (!reqNumRegex.test(reqNum)) { // $("#errorMessageCommon").css('display', 'block').text('Request Number must be in a proper format.'); //} //else if (!assmntNumRegex.test(assmntNum)) { // $("#errorMessageCommon").css('display', 'block').text('Assessment Number must be in a proper format.'); //} else { $("#errorMessageCommon").css('display', 'none'); viewDetails(reqNum, assmntNum); } }); $(document).on('keyup', function (e) { if (e.key === 'Enter' || e.keyCode === 13) { $("#viewRequestedDetails").click(); } }); function viewDetails(reqNum1, assmntNum1) { fetch(`/umbraco/surface/RequestDetails/GetRequestedDetails?reqNum=${reqNum1}&assmntNum=${assmntNum1}&culture=${curretnCulture}`) .then(response => response.json()) .then(data => { if (data.success && data.redirectUrl) { //window.location.href = data.redirectUrl; safeRedirect(data.redirectUrl, "Inquiry about Objection"); } else { console.log("Failed to get requested details."); } }) .catch(error => { console.error("Fetch error:", error); }); } $('#Inquiry-about-objections').on("hidden.bs.modal", function () { $("#requestNum").val(""); $("#assessmentNum").val(""); $("#errorMessageCommon").css('display', 'none'); }); $("#requestMoreInfo").on('click', function (e) { //e.preventDefault(); $("#requestGotDetails").css('display', 'none'); $('#missingInfo').css('display', 'block'); }); $("#requestDetails").on('click', function (e) { e.preventDefault(); var reqNum = $("#requestNo").val(); if (reqNum == "") { $("#errorMessage").css('display', 'block'); $("#errorMessage").text(requiredMessage); } else { $("#errorMessage").css('display', 'none'); viewRequestedDetails(reqNum); } }); function addTrailingSlash(path) { return path.endsWith("/") ? path : path + "/"; } function safeRedirect(homePageUrl, loggerType) { const allowedDomains = currentLang === "en-US" ? allowedDomainsEn : allowedDomainsAr; try { const url = new URL(homePageUrl, window.location.origin); const params = url.searchParams; // Check only these two params exist and no extra ones const keys = Array.from(params.keys()); if (keys.length !== 2 || !keys.includes('reqNum') || !keys.includes('assmntNum')) { fetch(`/umbraco/surface/DetectInvalidUrl/LogInvalidUrl?loggerType=${loggerType}&invalidUrl=${url.href}&e=${null}`) .then(response => { }).catch(error => { console.error("Fetch error:", error); }); return false; } const reqNumToCheck = params.get('reqNum')?.trim(); const assmntNumToCheck = params.get('assmntNum')?.trim(); if (!reqNumToCheck || !assmntNumToCheck) { fetch(`/umbraco/surface/DetectInvalidUrl/LogInvalidUrl?loggerType=${loggerType}&invalidUrl=${url.href}&e=${null}`) .then(response => { }).catch(error => { console.error("Fetch error:", error); }); return false; } // Regex for validation: // reqNum: alphanumeric and dash only const reqNumPattern = /^[a-zA-Z0-9-]+$/; // assmntNum: alphanumeric only, no dash const assmntNumPattern = /^[a-zA-Z0-9-]+$/; const isRelative = url.origin === window.location.origin; const isAllowedDomain = allowedMainDomains.some(domain => domain.endsWith('/') ? domain.slice(0, -1) : domain && url.origin.endsWith(domain)); if (isAllowedDomain && isRelative && allowedDomains.some(domain => addTrailingSlash(url.pathname).endsWith(domain)) && reqNumPattern.test(reqNumToCheck) && assmntNumPattern.test(assmntNumToCheck)) { window.location.href = url.href; } else { console.warn("Blocked unsafe redirect:", url.href); fetch(`/umbraco/surface/DetectInvalidUrl/LogInvalidUrl?loggerType=${loggerType}&invalidUrl=${url.href}&e=${null}`) .then(response => { }).catch(error => { console.error("Fetch error:", error); }); } } catch (e) { console.error("Invalid URL supplied:", homePageUrl); fetch(`/umbraco/surface/DetectInvalidUrl/LogInvalidUrl?loggerType=${loggerType}&invalidUrl=${url.href}&e=${e}`) .then(response => { }).catch(error => { console.error("Fetch error:", error); }); } } function viewRequestedDetails(reqNum1) { fetch(`/umbraco/surface/RequestDetails/GetRequestedNumDetails?reqNum=${reqNum1}&culture=${curretnCulture}`) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { if (data.success && data.redirectUrl) { //window.location.href = data.redirectUrl; safeRedirect(data.redirectUrl, "Inquiry about Objection"); } else { console.log("Failed to get requested details."); } }) .catch(error => { console.error("Fetch error:", error); }); } $('#Inquiry-about-objections-other').on("hidden.bs.modal", function () { $("#requestNo").val(""); }); var reqNoFromUrl = $('#requestNumb').val(); var assessmentNum = $('#assmntNum').val(); var formData = new FormData(); var fileInput = document.getElementById("fileInput"); const MAX_FILES = 5; const MAX_FILE_SIZE_MB = 10; const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024; let selectedFiles = []; // File signature map (add more as needed) const fileSignatures = { pdf: { signature: [0x25, 0x50, 0x44, 0x46], extensions: ['pdf'] }, png: { signature: [0x89, 0x50, 0x4E, 0x47], extensions: ['png'] }, jpg: { signature: [0xFF, 0xD8, 0xFF], extensions: ['jpg', 'jpeg'] } }; function getExtension(fileName) { return fileName.split('.').pop().toLowerCase(); } function matchesSignature(buffer, signature) { return signature.every((byte, index) => buffer[index] === byte); } function validateFileSignature(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = function (e) { const buffer = new Uint8Array(e.target.result); const ext = getExtension(file.name); let matchedType = null; for (const [type, { signature, extensions }] of Object.entries(fileSignatures)) { if (matchesSignature(buffer, signature)) { matchedType = { type, signature, extensions }; break; } } if (!matchedType) { // Unsupported file type resolve({ file, isValid: false, reason: 'unsupported', expectedTypes: Object.values(fileSignatures).flatMap(t => t.extensions) }); } else if (!matchedType.extensions.includes(ext)) { // Signature found, but extension mismatch resolve({ file, isValid: false, reason: 'mismatch', expectedTypes: matchedType.extensions }); } else { // Valid resolve({ file, isValid: true }); } }; reader.readAsArrayBuffer(file.slice(0, 8)); // Read first few bytes }); } $(document).on('change', '#fileInput', async function () { const newFiles = Array.from(this.files); let sizeErrorFiles = []; let invalidSignatureFiles = []; if ((selectedFiles.length + newFiles.length) > MAX_FILES) { $('#uploadStatus') .text(maxNumberFileUpload.replace("[FILENUM]", `${MAX_FILES}`)) .css('display', 'block'); this.value = ''; return; } for (const newFile of newFiles) { if (newFile.size > MAX_FILE_SIZE_BYTES) { sizeErrorFiles.push(newFile.name); continue; } const isDuplicate = selectedFiles.some(existingFile => existingFile.name === newFile.name && existingFile.size === newFile.size ); if (isDuplicate) continue; // ✅ Signature validation const validationResult = await validateFileSignature(newFile); if (!validationResult.isValid) { invalidSignatureFiles.push({ name: newFile.name, expectedTypes: validationResult.expectedTypes, reason: validationResult.reason }); continue; } selectedFiles.push(newFile); } if (sizeErrorFiles.length > 0) { $('#uploadStatus') .text(`${maxSizeFileUpload.replace("[FILESIZE]", "10MB")}\n${sizeErrorFiles.join(', ')}`) .css('display', 'block'); this.value = ''; return; } if (invalidSignatureFiles.length > 0) { $('#uploadStatus') .text(`${fileTypeUnsupportedError}`) .css('display', 'block'); this.value = ''; return; } $('#uploadStatus').hide(); this.value = ''; // reset input updateFileDisplay(); }); function updateFileDisplay() { const fileNameDisplay = $('#selectedFileNames'); fileNameDisplay.empty(); if (selectedFiles.length > 0) { const ul = $(''); selectedFiles.forEach((file, index) => { const li = $('
  • ').text(file.name + ' '); const removeBtn = $(''); removeBtn.on('click', function () { selectedFiles.splice(index, 1); updateFileDisplay(); }); li.append(removeBtn); ul.append(li); }); fileNameDisplay.append(ul); $('#uploadStatus').hide(); } else { $('#uploadStatus').show().text(`${fileSelectError}`); } } // Submit via Fetch $(document).on('click', '#missingInfoSubmit', async function (e) { const reqType = parseInt($('#reqInfoType').val()); e.preventDefault(); if (window.validateStep1 && !window.validateStep1()) { // Prevent navigation console.warn("Validation failed"); return; } const missingName = $("input[name='missingName']"); const IdNum = $("input[name='missingIdNumber']"); if (reqType != undefined && (reqType == 2 || reqType == 3) && selectedFiles.length === 0) { $('#uploadStatus') .text(`${fileSelectError}`) .css('display', 'block'); return; } else { $('#uploadStatus') .text('') .css('display', 'none'); } const requestId = reqNoFromUrl; const filesData = []; const missingFieldsData = []; const tempMissing = []; const check1 = selectedIds; $('.missing-fields').each(function () { const id = $(this).attr('id'); if (selectedIds.includes(parseInt(id.split('-')[1]))) { tempMissing.push(id); } }); //tempMissing.push(IdNum.attr('id')); //tempMissing.push(missingName.attr('id')); for (const x of tempMissing) { missingFieldsData.push({ Id: x.split('-')[1], Value: $(`#${x}`).val() }); } for (const file of selectedFiles) { const base64 = await getBase64(file); filesData.push({ File: base64.split(',')[1], FileName: file.name }); } const check = missingFieldsData; const payload = { missingDocuments: filesData, ticketNumber: reqNoFromUrl, IdNumber: IdNum.val(), daCaseNumber: assessmentNum, requestInfoType: reqType, missingFields: missingFieldsData, }; const payloadJ = JSON.stringify(payload); fetch(`/umbraco/surface/RequestDetails/SubmitMissingInfo`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { if (!data.isSuccess) { //alert("❌ Failed: " + (data.message || "Unknown error")); $("#missingInfo").css('display', 'block'); $('#failUploadRequestMoreInfo').css('display', 'block'); $('#requestSubmitSuccess').css('display', 'none'); return; } else { $("#missingInfo").css('display', 'none'); $('#requestSubmitSuccess').css('display', 'block'); } }) .catch(error => { console.error('Error:', error); alert("❌ Failed Data Submission"); }); }); function getBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } $("#backToRequestedDetails").on('click', function (e) { e.preventDefault(); $("#missingInfo").css('display', 'none'); $('#requestGotDetails').css('display', 'block'); }); $(document).on('click', '.download-documents', function () { const count = $(this).attr('id').split('-')[1]; // "doc-0" => "0" const fileId = $(`#fileId-${count}`).val(); GetDocuemntBase64(fileId); }); function GetDocuemntBase64(fileId) { const loader = document.getElementById("loader"); // Show loader and clear previous slots loader.style.display = "block"; fetch(`/umbraco/surface/RequestDetails/GetDocuments`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(fileId) }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { if (!data.isSuccess) { alert(data.message); return; } else { const base64Data = data.data.file; const fileName = data.data.fileName || 'downloaded-file'; const mimeType = detectMimeType(base64Data); const fileUrl = `data:${mimeType};base64,${base64Data}`; loader.style.display = "none"; if (mimeType.startsWith('image/')) { // Open image in modal showImageInModal(fileUrl, fileName); } else if (mimeType === 'application/pdf') { // ✅ Validate PDF base64 (must start with "JVBER") if (!base64Data || !base64Data.startsWith("JVBER")) { alert("⚠️ This is not a valid PDF file."); console.error("Invalid base64 (first 20 chars):", base64Data.substring(0, 20)); return; } // ✅ Convert base64 to Blob const blob = base64ToBlob(base64Data, mimeType); const blobUrl = URL.createObjectURL(blob); // ✅ Open PDF in new tab const newTab = window.open(blobUrl, '_blank'); if (!newTab) { alert("⚠️ Please allow pop-ups to view the PDF."); } } } }) .catch(error => { console.error('Error:', error); alert("❌ Failed Download"); }).finally(() => { loader.style.display = "none"; }); } // ✅ Helper: Convert base64 string to Blob function base64ToBlob(base64, mimeType) { const byteChars = atob(base64); const byteArrays = []; for (let offset = 0; offset < byteChars.length; offset += 512) { const slice = byteChars.slice(offset, offset + 512); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } byteArrays.push(new Uint8Array(byteNumbers)); } return new Blob(byteArrays, { type: mimeType }); } function detectMimeType(base64) { if (base64.startsWith('JVBER')) return 'application/pdf'; if (base64.startsWith('/9j/')) return 'image/jpeg'; if (base64.startsWith('iVBOR')) return 'image/png'; return 'application/octet-stream'; // fallback } function showImageInModal(imageSrc, fileName) { const modalId = 'attachments-images-preview'; const modalElement = document.getElementById(modalId); const modalBody = modalElement.querySelector('.modal-body .mb-4'); // Clear existing content and inject image modalBody.innerHTML = ` Image Preview `; // Show modal using Bootstrap 5 const modalInstance = new bootstrap.Modal(modalElement); modalInstance.show(); const imgName = document.getElementById('modalImageDocName'); imgName.textContent = fileName; document.getElementById('attachments-images-preview') .addEventListener('hidden.bs.modal', function () { const modalBody = this.querySelector('.modal-body .mb-4'); modalBody.innerHTML = ''; }); } });