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

<?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");
}