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 @@
+
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) {