diff --git a/config/config.yaml b/config/config.yaml index 3fb7a7a999f..dcecf5392f3 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -221,7 +221,13 @@ assets: script: jszip.min.js jspdf: basePath: '%assets_static_relative%/jspdf/dist/' - script: jspdf.umd.min.js + script: jspdf.min.js + jspdfdebug: + basePath: '%assets_static_relative%/jspdf/dist/' + script: jspdf.debug.js + jstiff: + basePath: '%assets_static_relative%/tiff/' + script: tiff.min.js dwv: basePath: '%assets_static_relative%/dwv/' script: diff --git a/interface/modules/custom_modules/oe-module-faxsms/contact.php b/interface/modules/custom_modules/oe-module-faxsms/contact.php index 055b7b58671..e6a82db6009 100644 --- a/interface/modules/custom_modules/oe-module-faxsms/contact.php +++ b/interface/modules/custom_modules/oe-module-faxsms/contact.php @@ -96,68 +96,77 @@ } } // when the form is submitted - $('#contact-form').on('submit', function (e) { - if (!e.isDefaultPrevented()) { - let wait = '
'; - let url = 'sendFax?type=fax'; - if (isSms) { - url = 'sendSMS?type=sms'; - } - if (isForward) { - url = 'forwardFax?type=fax'; - } - if (isEmail) { - url = 'sendEmail?type=email'; - } - if (isOnetime) { - url = "./library/api_onetime.php?"; - if (isSms) { - url += 'sendOneTime&type=sms' - } else { - url += 'sendOneTime&type=email'; - } - } + $(document).ready(function() { + // Ensuring event handlers are set after the DOM is fully loaded + $('#contact-form').on('submit', function(e) { + e.preventDefault(); // Prevent the default form submit + + const wait = '
'; $('#contact-form').find('.messages').html(wait); - // POST values in the background the script URL + + const url = buildUrl(); + const formData = $(this).serialize(); + $.ajax({ type: "POST", url: url, - data: $(this).serialize(), - success: function (data) { - try { - let t_data = JSON.parse(data); - data = t_data; - } catch (e) { - } - let err = (data.search(/Exception/) !== -1 ? 1 : 0); - if (!err) { - err = (data.search(/Error:/) !== -1) ? 1 : 0; - } - // Type of the message: success or danger. Apply it to the alert. - let messageAlert = 'alert-' + (err !== 0 ? 'danger' : 'success'); - let messageText = data; - - // let's compose alert box HTML - let alertBox = '
' + - '' + messageText + '
'; - - // If we have messageAlert and messageText - if (messageAlert && messageText) { - // inject the alert to messages div in our form - $('#contact-form').find('.messages').html(alertBox); - setTimeout(function () { - if (!err) { - // close dialog as we have success. - dlgclose(); - } - // if error let user close dialog for time to read error message. - }, 4000); - } + data: formData, + success: handleResponse, + error: function() { + showErrorMessage('An unexpected error occurred and your request could not be completed.'); } }); - return false; + }); + + function buildUrl() { + // Simplify logic by directly mapping conditions to URLs + if (isOnetime) { + const type = isSms ? 'sms' : 'email'; + return `./library/api_onetime.php?sendOneTime&type=${encodeURIComponent(type)}`; + } + + if (isSms) { + return 'sendSMS?type=sms'; + } else if (isForward) { + return 'forwardFax?type=fax'; + } else if (isEmail) { + return 'sendEmail?type=email'; + } else { + return 'sendFax?type=fax'; + } + } + + function handleResponse(data) { + let jsonData; + try { + jsonData = JSON.parse(data); + } catch (e) { + jsonData = data; // Use data as is if it can't be parsed as JSON + } + + const isError = /Exception|Error:/.test(jsonData); + const messageType = isError ? 'danger' : 'success'; + const messageText = jsonData; + const alertBox = `
+ + ${messageText} +
`; + + $('#contact-form').find('.messages').html(alertBox); + if (!isError) { + setTimeout(() => { dlgclose(); }, 4000); // Auto-close dialog on success + } } - }) + + function showErrorMessage(message) { + const alertBox = `
+ + ${message} +
`; + $('#contact-form').find('.messages').html(alertBox); + } + }); + }); function sel_patient() { diff --git a/interface/modules/custom_modules/oe-module-faxsms/messageUI.php b/interface/modules/custom_modules/oe-module-faxsms/messageUI.php index ead97fac620..ef532d476ba 100644 --- a/interface/modules/custom_modules/oe-module-faxsms/messageUI.php +++ b/interface/modules/custom_modules/oe-module-faxsms/messageUI.php @@ -6,7 +6,7 @@ * @package OpenEMR * @link http://www.open-emr.org * @author Jerry Padgett - * @copyright Copyright (c) 2018-2023 Jerry Padgett + * @copyright Copyright (c) 2018-2024 Jerry Padgett * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 */ @@ -26,13 +26,14 @@ + <?php echo $tabTitle ?? ''; ?> verifyAcl()) { die("

" . xlt("Not Authorised!") . "

"); } - Header::setupHeader(['opener', 'datetime-picker']); + Header::setupHeader(['opener', 'datetime-picker', 'jspdf', 'jstiff']); echo ""; ?> @@ -47,14 +48,14 @@ require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?> }); - var dateRange = new Date(new Date().setDate(new Date().getDate() - 7)); + let dateRange = new Date(new Date().setDate(new Date().getDate() - 7)); $("#fromdate").val(dateRange.toJSON().slice(0, 10)); $("#todate").val(new Date().toJSON().slice(0, 10)); $(".other").hide(); - if (currentService === '2') { + if (currentService == '2') { $(".etherfax").hide(); - } else if (currentService === '3') { + } else if (currentService == '3') { $(".twilio").hide(); $(".etherfax-hide").hide(); $(".etherfax").show(); @@ -92,14 +93,14 @@ } catch (error) { console.log('Session restore failed!'); } - let msg = ; + let msg = ; if (e === 'live') { let yn = confirm(msg); if (!yn) { return false } } - let msg1 = ; + let msg1 = ; dlgopen(ppath, '_blank', 1240, 900, true, msg1) }; @@ -110,7 +111,7 @@ console.log('Session restore failed!'); } e.preventDefault(); - let msg = ; + let msg = ; dlgopen('', 'setup', 'modal-md', 700, '', msg, { buttons: [ {text: 'Cancel', close: true, style: 'secondary btn-sm'} @@ -119,16 +120,6 @@ }); }; - function base64ToArrayBuffer(_base64Str) { - let binaryString = window.atob(_base64Str); - let binaryLen = binaryString.length; - let bytes = new Uint8Array(binaryLen); - for (let i = 0; i < binaryLen; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; - } - const forwardFax = function (e, docid = '', filePath = '', details = []) { let btnClose = ; let title = ; @@ -142,6 +133,16 @@ function base64ToArrayBuffer(_base64Str) { return false; }; + function base64ToArrayBuffer(_base64Str) { + let binaryString = window.atob(_base64Str); + let binaryLen = binaryString.length; + let bytes = new Uint8Array(binaryLen); + for (let i = 0; i < binaryLen; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + function showPrint(base64, _contentType = 'image/tiff') { const binary = atob(base64.replace(/\s/g, '')); const len = binary.length; @@ -166,64 +167,8 @@ function showPrint(base64, _contentType = 'image/tiff') { iframe.src = url; } - function showDocument(_base64, _contentType = 'image/tiff') { - const binary = atob(_base64.replace(/\s/g, '')); - const len = binary.length; - const buffer = new ArrayBuffer(len); - const view = new Uint8Array(buffer); - for (let i = 0; i < len; i++) { - view[i] = binary.charCodeAt(i); - } - const blob = new Blob([view], {type: _contentType}); - const dataUrl = URL.createObjectURL(blob); - let width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? - document.documentElement.clientWidth : screen.width; - let height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? - document.documentElement.clientHeight : screen.height; - height = screen.height ? screen.height * 0.95 : height; - let left = (width / 4); - let top = '10'; - let win = window.open( - '', '', - 'toolbar=0, location=0, directories=0, status=0, menubar=0,' + - ' scrollbars=0, resizable=0, copyhistory=0, ' + - 'width=' + width / 1.75 + ', height=' + height + - ', top=' + top + ', left=' + left - ); - if (win === null) { - alert(xl('Please allow popups for this site')); - } else { - win.document.write(""); - } - } - - function viewDocument(e = '', dataUrl) { - if (e !== '') { - e.preventDefault(); - e.stopPropagation(); - } - let width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? - document.documentElement.clientWidth : screen.width; - let height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? - document.documentElement.clientHeight : screen.height; - height = screen.height ? screen.height * 0.95 : height; - let left = (width / 4); - let top = '10'; - let win = window.open( - '', '', - 'toolbar=0, location=0, directories=0, status=0, menubar=0,' + - ' scrollbars=0, resizable=0, copyhistory=0, ' + - 'width=' + width / 1.75 + ', height=' + height + - ', top=' + top + ', left=' + left - ); - if (win === null) { - alert(xl('Please allow popups for this site')); - } else { - win.document.write(""); - } - } - - function getDocument(e, docuri, docid, downFlag, deleteFlag = '') { + // Function to get or dispose of document. + async function getDocument(e, docuri, docid, downFlag, deleteFlag = '') { try { top.restoreSession(); } catch (error) { @@ -237,8 +182,8 @@ function getDocument(e, docuri, docid, downFlag, deleteFlag = '') { } if (downFlag == 'true') { let yn = confirm( - xl("After downloading a fax it is marked as received and no longer available.") + "\n\n" + - xl("Do you want to continue with download?") + xl("After a fax is downloaded it is marked as received and no longer available here.") + "\n\n" + + xl("Do you want to continue with this download?") ); if (!yn) { return false; @@ -252,17 +197,20 @@ function getDocument(e, docuri, docid, downFlag, deleteFlag = '') { return false; } } + // Get ready, Get set, Go! let actionUrl = 'viewFax?type=fax'; - $("#brand").addClass('fa fa-spinner fa-spin'); - return $.post(actionUrl, { - 'type': serviceType, - 'docuri': docuri, - 'docid': docid, - 'pid': pid, - 'download': downFlag, - 'delete': deleteFlag - }).done(function (json) { - $("#brand").removeClass('fa fa-spinner fa-spin'); + $(".brand").addClass('fa fa-spinner fa-spin'); + try { + let json = await $.post(actionUrl, { + 'type': serviceType, + 'docuri': docuri, + 'docid': docid, + 'pid': pid, + 'download': downFlag, + 'delete': deleteFlag + }).promise(); + $(".brand").removeClass('fa fa-spinner fa-spin'); + let data; try { data = JSON.parse(json); } catch { @@ -273,12 +221,135 @@ function getDocument(e, docuri, docid, downFlag, deleteFlag = '') { return false; } if (downFlag == 'true') { - location.href = "disposeDoc?type=fax&file_path=" + encodeURIComponent(data); - setTimeout(retrieveMsgs, 3000); + let base64 = data.base64; + if (data.mime === 'image/tiff' || data.mime === 'image/tif') { + let images = await convertTiffToImages(base64ToArrayBuffer(data.base64)); + base64 = await convertImagesToPdf(images, data.filename); + } else { + base64 = ''; + } + fetch('disposeDocument?type=fax', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + action: 'setup', + file_path: data.path, + content: base64 + }) + }) + .then(response => response.json()) + .then(result => { + // Download the file. result.url is temp file path of tiff to pdf by image conversion in JS or imagick. + if (result.success) { + location.href = "disposeDocument?type=fax&action=download&file_path=" + encodeURIComponent(result.url); + } else { + console.error('Failed to prepare the file for download:', jsText(result.message)); + } + }) + .catch(error => { + console.error('Error:', error); + }); + return false; } - showDocument(data.base64, data.mime); - }); + if (data.mime === 'application/pdf') { + showDocument(data.base64, data.mime); + } else if (data.mime === 'image/tiff') { + let images = await convertTiffToImages(base64ToArrayBuffer(data.base64)); + let pdfBase64 = await convertImagesToPdf(images, data.filename); + showDocument(pdfBase64.replace('data:application/pdf;base64,', ''), 'application/pdf'); + } else { + showDocument(data.base64, data.mime); + } + } catch (error) { + console.error('Error handling document:', jsText(error)); + $(".brand").removeClass('fa fa-spinner fa-spin'); + } + } + + // Function to convert TIFF to PNG/JPEG images + async function convertTiffToImages(tiffData, mime = 'image/jpeg') { + try { + let tiff = new Tiff({buffer: tiffData}); + const directories = tiff.countDirectory(); + const promises = []; + + for (let i = 0; i < directories; i++) { + promises.push(new Promise((resolve, reject) => { + tiff.setDirectory(i); + let canvas = tiff.toCanvas(); + let imageData = canvas.toDataURL(mime); + resolve(imageData); + })); + } + + return Promise.all(promises); + } catch (error) { + console.error('Failed to convert TIFF to images:', error); + return []; + } + } + + // Function to convert images to PDF and return a base64 + async function convertImagesToPdf(images, filename = 'fax-tiff-to-pdf.pdf') { + const doc = new jsPDF(); + const pageHeight = doc.internal.pageSize.getHeight(); + + for (let i = 0; i < images.length; i++) { + if (i !== 0) { + doc.addPage(); + } + doc.addImage(images[i], 'JPEG', 10, 10, 190, pageHeight - 20); + } + + return doc.output('datauristring').split(',')[1]; // Return only the Base64 part + } + + function showDocument(_base64, _contentType = 'image/tiff') { + try { + // Log the type and value of _base64 to debug + console.log('Type of _base64:', typeof _base64); + console.log('Content of _base64:', _base64); + + // Ensure _base64 is a string + if (typeof _base64 !== 'string') { + throw new TypeError('Expected a base64 string'); + } + + // Remove any whitespace in the base64 string + const cleanedBase64 = _base64.replace(/\s/g, ''); + const binary = atob(cleanedBase64); + const len = binary.length; + const buffer = new ArrayBuffer(len); + const view = new Uint8Array(buffer); + + for (let i = 0; i < len; i++) { + view[i] = binary.charCodeAt(i); + } + + const blob = new Blob([view], { type: _contentType }); + const dataUrl = URL.createObjectURL(blob); + displayInNewWindow(dataUrl); + } catch (e) { + console.error('Error decoding base64 or displaying document:', e); + alert('Failed to display the document due to an invalid document format.'); + } + } + + function displayInNewWindow(dataUrl) { + let width = window.innerWidth || document.documentElement.clientWidth || screen.width; + let height = window.innerHeight || document.documentElement.clientHeight || screen.height; + height = screen.height ? screen.height * 0.95 : height; + let left = (width / 4); + let top = '10'; + let win = window.open('', '', 'toolbar=0, location=0, directories=0, status=0, menubar=0, scrollbars=0, resizable=0, copyhistory=0, width=' + width / 1.75 + ', height=' + height + ', top=' + top + ', left=' + left); + if (win === null) { + alert(xl('Please allow popups for this site')); + } else { + win.document.write(""); + } } // SMS status @@ -300,7 +371,7 @@ function retrieveMsgs(e = '', req = '') { let datefrom = $('#fromdate').val(); let dateto = $('#todate').val(); let data = []; - $("#brand").addClass('fa fa-spinner fa-spin'); + $(".brand").addClass('fa fa-spinner fa-spin'); $("#rcvdetails tbody").empty(); $("#sent-details tbody").empty(); $("#msgdetails tbody").empty(); @@ -313,11 +384,11 @@ function retrieveMsgs(e = '', req = '') { }, function () { }, 'json').done(function (data) { if (data.error) { - $("#brand").removeClass('fa fa-spinner fa-spin'); + $(".brand").removeClass('fa fa-spinner fa-spin'); alertMsg(data.error); return false; } - // populate our panels + // populate our cards $("#rcvdetails tbody").empty().append(data[0]); $("#sent-details tbody").empty().append(data[1]); $("#msgdetails tbody").empty().append(data[2]); @@ -328,7 +399,7 @@ function retrieveMsgs(e = '', req = '') { }).fail(function (xhr, status, error) { alertMsg(, 7000); }).always(function () { - $("#brand").removeClass('fa fa-spinner fa-spin'); + $(".brand").removeClass('fa fa-spinner fa-spin'); }); } @@ -344,7 +415,7 @@ function getLogs() { let datefrom = $('#fromdate').val(); let dateto = $('#todate').val(); - $("#brand").addClass('fa fa-spinner fa-spin'); + $(".brand").addClass('fa fa-spinner fa-spin'); return $.post(actionUrl, { 'type': serviceType, 'pid': pid, @@ -364,7 +435,7 @@ function getLogs() { getNotificationLog(); } }).always(function () { - $("#brand").removeClass('fa fa-spinner fa-spin'); + $(".brand").removeClass('fa fa-spinner fa-spin'); }); } @@ -379,7 +450,7 @@ function getNotificationLog() { let datefrom = $('#fromdate').val() + " 00:00:01"; let dateto = $('#todate').val() + " 23:59:59"; - $("#brand").addClass('fa fa-spinner fa-spin'); + $(".brand").addClass('fa fa-spinner fa-spin'); return $.post(actionUrl, { 'type': serviceType, 'pid': pid, @@ -395,7 +466,7 @@ function getNotificationLog() { } $("#alertdetails tbody").empty().append(data); }).always(function () { - $("#brand").removeClass('fa fa-spinner fa-spin'); + $(".brand").removeClass('fa fa-spinner fa-spin'); }); } @@ -431,7 +502,7 @@ function toggleDetail(id) { return false; } - function notifyUser(e, faxId, recordId, pid=0) { + function notifyUser(e, faxId, recordId, pid = 0) { e.preventDefault(); let btnClose = ; let url = top.webroot_url + @@ -447,11 +518,13 @@ function createPatient(e, faxId, recordId, data) { let url = './library/utility.php?pop_add_new=1&recId=' + encodeURIComponent(recordId) + "&jobId=" + encodeURIComponent(faxId) + "&data=" + encodeURIComponent(data); dlgopen(url, 'create_patient', 'modal-md', 'full', '', '', { - buttons: [{text: btnClose, close: true, style: 'primary'}], - sizeHeight: 'full'} + buttons: [{text: btnClose, close: true, style: 'primary'}], + sizeHeight: 'full' + } ); return false; } + // drop bucket const queueMsg = '' + ; Dropzone.autoDiscover = false; @@ -500,7 +573,7 @@ function createPatient(e, faxId, recordId, data) {
@@ -539,7 +612,7 @@ function createPatient(e, faxId, recordId, data) {
-

+