You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
12 KiB
317 lines
12 KiB
2 years ago
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Functions to transmit a CCD as a Direct Protocol Message
|
||
|
*
|
||
|
* Copyright (C) 2013, 2021 EMR Direct <https://www.emrdirect.com/>
|
||
|
*
|
||
|
* Use of these functions requires an active phiMail Direct messaging
|
||
|
* account with EMR Direct. For information regarding this service,
|
||
|
* please visit http://www.emrdirect.com or email support@emrdirect.com
|
||
|
*
|
||
|
* LICENSE: This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; either version 3
|
||
|
* of the License, or (at your option) any later version.
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
|
||
|
*
|
||
|
* @package OpenEMR
|
||
|
* @author EMR Direct <https://www.emrdirect.com/>
|
||
|
* @link http://www.open-emr.org
|
||
|
*/
|
||
|
|
||
|
require_once(dirname(__FILE__) . "/../library/patient.inc");
|
||
|
require_once(dirname(__FILE__) . "/../library/direct_message_check.inc");
|
||
|
|
||
|
use OpenEMR\Common\Crypto\CryptoGen;
|
||
|
use OpenEMR\Common\Logging\EventAuditLogger;
|
||
|
use OpenEMR\Common\DirectMessaging\ErrorConstants;
|
||
|
|
||
|
/*
|
||
|
* Connect to a phiMail Direct Messaging server and transmit
|
||
|
* a message to the specified recipient. If the message is accepted by the
|
||
|
* server, the script will return "SUCCESS", otherwise it will return an error msg.
|
||
|
* @param string message The message to send via Direct
|
||
|
* @param string recipient the Direct Address of the recipient
|
||
|
* @param bool Whether to force receipt confirmation that the message was delivered. Can cause message delivery failures if recipient system does not support the option.
|
||
|
* @return string result of operation
|
||
|
*/
|
||
|
function transmitMessage($message, $recipient, $verifyFinalDelivery = false)
|
||
|
{
|
||
|
|
||
|
$reqBy = $_SESSION['authUser'];
|
||
|
$reqID = $_SESSION['authUserID'];
|
||
|
|
||
|
$config_err = xl(ErrorConstants::MESSAGING_DISABLED) . " " . ErrorConstants::ERROR_CODE_ABBREVIATION . ":";
|
||
|
if ($GLOBALS['phimail_enable'] == false) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGING_DISABLED);
|
||
|
}
|
||
|
|
||
|
$fp = phimail_connect($err);
|
||
|
if ($fp === false) {
|
||
|
return("$config_err $err");
|
||
|
}
|
||
|
|
||
|
$phimail_username = $GLOBALS['phimail_username'];
|
||
|
$cryptoGen = new CryptoGen();
|
||
|
$phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
|
||
|
$ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
|
||
|
if ($ret !== true) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_AUTH_FAILED);
|
||
|
}
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, "TO $recipient\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::RECIPIENT_NOT_ALLOWED) . " " . $ret );
|
||
|
}
|
||
|
|
||
|
$ret = fgets($fp, 1024); //ignore extra server data
|
||
|
|
||
|
$text_out = $message;
|
||
|
|
||
|
$text_len = strlen($text_out);
|
||
|
phimail_write($fp, "TEXT $text_len\n");
|
||
|
$ret = @fgets($fp, 256);
|
||
|
if ($ret != "BEGIN\n") {
|
||
|
phimail_close($fp);
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_FAILED);
|
||
|
}
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, $text_out);
|
||
|
if ($ret !== true) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_OK_FAILED);
|
||
|
}
|
||
|
|
||
|
if ($verifyFinalDelivery) {
|
||
|
$ret = phimail_write_expect_OK($fp, "SET FINAL 1\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
|
||
|
}
|
||
|
} else {
|
||
|
$ret = phimail_write_expect_OK($fp, "SET FINAL 0\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
phimail_write($fp, "SEND\n");
|
||
|
$ret = fgets($fp);
|
||
|
phimail_write($fp, "OK\n");
|
||
|
phimail_close($fp);
|
||
|
|
||
|
|
||
|
if (substr($ret, 5) == "ERROR") {
|
||
|
//log the failure
|
||
|
|
||
|
EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret);
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_FILE_SEND_FAILED));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If we get here, the message was successfully sent and the return
|
||
|
* value $ret is of the form "QUEUED recipient message-id" which
|
||
|
* is suitable for logging.
|
||
|
*/
|
||
|
$msg_id = explode(" ", trim($ret), 4);
|
||
|
if ($msg_id[0] != "QUEUED" || !isset($msg_id[2])) { //unexpected response
|
||
|
$ret = "UNEXPECTED RESPONSE: " . $ret;
|
||
|
EventAuditLogger::instance()->newEvent("transmit-message", $reqBy, $_SESSION['authProvider'], 0, $ret);
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
|
||
|
}
|
||
|
|
||
|
EventAuditLogger::instance()->newEvent("transmit-message", $reqBy, $_SESSION['authProvider'], 1, $ret);
|
||
|
$adodb = $GLOBALS['adodb']['db'];
|
||
|
$sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
|
||
|
"VALUES ('S', ?, ?, ?, 'S', NOW(), ?)";
|
||
|
$res = @sqlStatementNoLog($sql, array($msg_id[2],$phimail_username,$recipient,$reqID));
|
||
|
|
||
|
return("SUCCESS");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Connect to a phiMail Direct Messaging server and transmit
|
||
|
* a CCD document to the specified recipient. If the message is accepted by the
|
||
|
* server, the script will return "SUCCESS", otherwise it will return an error msg.
|
||
|
* @param number The patient pid that we are sending a CCDA doc for
|
||
|
* @param string ccd the data to transmit, a CCDA document is assumed
|
||
|
* @param string recipient the Direct Address of the recipient
|
||
|
* @param string requested_by user | patient
|
||
|
* @param string The format that the document is in (pdf, xml, html)
|
||
|
* @param string The message body the clinician wants to send
|
||
|
* @param string The filename to use as the name of the attachment (must included file extension as part of filename)
|
||
|
* @param bool Whether to force receipt confirmation that the message was delivered. Can cause message delivery failures if recipient system does not support the option.
|
||
|
* @return string result of operation
|
||
|
*/
|
||
|
function transmitCCD($pid, $ccd_out, $recipient, $requested_by, $xml_type = "CCD", $format_type = 'xml', $message = '', $filename = '', $verifyFinalDelivery = false): string
|
||
|
{
|
||
|
//get patient name in Last_First format (used for CCDA filename) and
|
||
|
//First Last for the message text.
|
||
|
$patientData = getPatientPID(array("pid" => $pid));
|
||
|
if (empty($patientData)) { // shouldn't ever happen but we need to check anyways
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
|
||
|
}
|
||
|
$patientName2 = "";
|
||
|
$att_filename = "";
|
||
|
|
||
|
// TODO: do we want to throw an error if we can't get patient data? Probably.
|
||
|
|
||
|
if (!empty($patientData[0]['lname'])) {
|
||
|
$patientName2 = trim($patientData[0]['fname'] . " " . $patientData[0]['lname']);
|
||
|
}
|
||
|
|
||
|
if (!empty($filename)) {
|
||
|
// if we have a filename from our database, we want to send that
|
||
|
$att_filename = $filename;
|
||
|
$extension = ""; // no extension needed
|
||
|
} else if (!empty($patientName2)) {
|
||
|
//spaces are the argument delimiter for the phiMail API calls and must be removed
|
||
|
// CCDA format requires patient name in last, first format
|
||
|
$att_filename = str_replace(" ", "_", $xml_type . "_" . $patientData[0]['lname']
|
||
|
. "_" . $patientData[0]['fname']);
|
||
|
$extension = "." . $format_type;
|
||
|
}
|
||
|
|
||
|
$config_err = xl(ErrorConstants::MESSAGING_DISABLED) . " " . ErrorConstants::ERROR_CODE_ABBREVIATION . ":";
|
||
|
if ($GLOBALS['phimail_enable'] == false) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGING_DISABLED);
|
||
|
}
|
||
|
|
||
|
$fp = phimail_connect($err);
|
||
|
if ($fp === false) {
|
||
|
return("$config_err $err");
|
||
|
}
|
||
|
|
||
|
$phimail_username = $GLOBALS['phimail_username'];
|
||
|
$cryptoGen = new CryptoGen();
|
||
|
$phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
|
||
|
$ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
|
||
|
if ($ret !== true) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_AUTH_FAILED);
|
||
|
}
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, "TO $recipient\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::RECIPIENT_NOT_ALLOWED) . " " . $ret );
|
||
|
}
|
||
|
|
||
|
$ret = fgets($fp, 1024); //ignore extra server data
|
||
|
|
||
|
// add whatever the clinican added as a message to be sent.
|
||
|
if (is_string($message) && trim($message) != "") {
|
||
|
$text_out = $message . "\n";
|
||
|
}
|
||
|
|
||
|
if ($requested_by == "patient") {
|
||
|
$text_out .= xl("Delivery of the attached clinical document was requested by the patient") .
|
||
|
($patientName2 == "" ? "." : ", " . $patientName2 . ".");
|
||
|
} else {
|
||
|
$text_out .= xl("A clinical document is attached") .
|
||
|
($patientName2 == "" ? "." : " " . xl("for patient") . " " . $patientName2 . ".");
|
||
|
}
|
||
|
|
||
|
|
||
|
$text_len = strlen($text_out);
|
||
|
phimail_write($fp, "TEXT $text_len\n");
|
||
|
$ret = @fgets($fp, 256);
|
||
|
if ($ret != "BEGIN\n") {
|
||
|
phimail_close($fp);
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_FAILED);
|
||
|
}
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, $text_out);
|
||
|
if ($ret !== true) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_OK_FAILED);
|
||
|
}
|
||
|
|
||
|
// MU2 CareCoordination added the need to send CCDAs formatted as html,pdf, or xml
|
||
|
if ($format_type == 'html') {
|
||
|
$add_type = "TEXT";
|
||
|
} else if ($format_type == 'pdf') {
|
||
|
$add_type = "RAW";
|
||
|
} else if ($format_type == 'xml') {
|
||
|
$add_type = $xml_type == "CCR" ? "CCR" : "CDA";
|
||
|
} else {
|
||
|
// unsupported format
|
||
|
return ("$config_err " . ErrorConstants::ERROR_CODE_INVALID_FORMAT_TYPE);
|
||
|
}
|
||
|
|
||
|
$ccd_len = strlen($ccd_out);
|
||
|
|
||
|
phimail_write($fp, "ADD " . $add_type . " " . $ccd_len . " " . $att_filename . $extension . "\n");
|
||
|
$ret = fgets($fp, 256);
|
||
|
if ($ret != "BEGIN\n") {
|
||
|
phimail_close($fp);
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_ADD_FILE_FAILED);
|
||
|
}
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, $ccd_out);
|
||
|
if ($ret !== true) {
|
||
|
return("$config_err " . ErrorConstants::ERROR_CODE_ADD_FILE_CONFIRM_FAILED);
|
||
|
}
|
||
|
|
||
|
if ($verifyFinalDelivery) {
|
||
|
$ret = phimail_write_expect_OK($fp, "SET FINAL 1\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
|
||
|
}
|
||
|
} else {
|
||
|
$ret = phimail_write_expect_OK($fp, "SET FINAL 0\n");
|
||
|
if ($ret !== true) {
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
phimail_write($fp, "SEND\n");
|
||
|
$ret = fgets($fp);
|
||
|
phimail_write($fp, "OK\n");
|
||
|
phimail_close($fp);
|
||
|
|
||
|
if ($requested_by == "patient") {
|
||
|
$reqBy = "portal-user";
|
||
|
$sql = "SELECT id FROM users WHERE username='portal-user'";
|
||
|
if (
|
||
|
($r = sqlStatementNoLog($sql)) === false ||
|
||
|
($u = sqlFetchArray($r)) === false
|
||
|
) {
|
||
|
$reqID = 1; //default if we don't have a service user
|
||
|
} else {
|
||
|
$reqID = $u['id'];
|
||
|
}
|
||
|
} else {
|
||
|
$reqBy = $_SESSION['authUser'];
|
||
|
$reqID = $_SESSION['authUserID'];
|
||
|
}
|
||
|
|
||
|
if (substr($ret, 5) == "ERROR") {
|
||
|
//log the failure
|
||
|
|
||
|
EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret, $pid);
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_FILE_SEND_FAILED));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If we get here, the message was successfully sent and the return
|
||
|
* value $ret is of the form "QUEUED recipient message-id" which
|
||
|
* is suitable for logging.
|
||
|
*/
|
||
|
$msg_id = explode(" ", trim($ret), 4);
|
||
|
if ($msg_id[0] != "QUEUED" || !isset($msg_id[2])) { //unexpected response
|
||
|
$ret = "UNEXPECTED RESPONSE: " . $ret;
|
||
|
EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret, $pid);
|
||
|
return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
|
||
|
}
|
||
|
|
||
|
EventAuditLogger::instance()->newEvent("transmit-" . $xml_type, $reqBy, $_SESSION['authProvider'], 1, $ret, $pid);
|
||
|
$adodb = $GLOBALS['adodb']['db'];
|
||
|
$sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,patient_id,user_id) " .
|
||
|
"VALUES ('S', ?, ?, ?, 'S', NOW(), ?, ?)";
|
||
|
$res = @sqlStatementNoLog($sql, array($msg_id[2],$phimail_username,$recipient,$pid,$reqID));
|
||
|
|
||
|
return("SUCCESS");
|
||
|
}
|