fix: For prior PR #6749 (#6803)
[openemr.git] / ccr / transmitCCD.php
blob4da3fe4228096e0b60a955455dd3ad3edade635d
1 <?php
3 /**
4 * Functions to transmit a CCD as a Direct Protocol Message
6 * Copyright (C) 2013, 2021 EMR Direct <https://www.emrdirect.com/>
8 * Use of these functions requires an active phiMail Direct messaging
9 * account with EMR Direct. For information regarding this service,
10 * please visit http://www.emrdirect.com or email support@emrdirect.com
12 * LICENSE: This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 3
15 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
23 * @package OpenEMR
24 * @author EMR Direct <https://www.emrdirect.com/>
25 * @link http://www.open-emr.org
28 require_once(dirname(__FILE__) . "/../library/patient.inc.php");
29 require_once(dirname(__FILE__) . "/../library/direct_message_check.inc.php");
31 use OpenEMR\Common\Crypto\CryptoGen;
32 use OpenEMR\Common\Logging\EventAuditLogger;
33 use OpenEMR\Common\DirectMessaging\ErrorConstants;
36 * Connect to a phiMail Direct Messaging server and transmit
37 * a message to the specified recipient. If the message is accepted by the
38 * server, the script will return "SUCCESS", otherwise it will return an error msg.
39 * @param string message The message to send via Direct
40 * @param string recipient the Direct Address of the recipient
41 * @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.
42 * @return string result of operation
44 function transmitMessage($message, $recipient, $verifyFinalDelivery = false)
47 $reqBy = $_SESSION['authUser'];
48 $reqID = $_SESSION['authUserID'];
50 $config_err = xl(ErrorConstants::MESSAGING_DISABLED) . " " . ErrorConstants::ERROR_CODE_ABBREVIATION . ":";
51 if ($GLOBALS['phimail_enable'] == false) {
52 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGING_DISABLED);
55 $fp = phimail_connect($err);
56 if ($fp === false) {
57 return("$config_err $err");
60 $phimail_username = $GLOBALS['phimail_username'];
61 $cryptoGen = new CryptoGen();
62 $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
63 $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
64 if ($ret !== true) {
65 return("$config_err " . ErrorConstants::ERROR_CODE_AUTH_FAILED);
68 $ret = phimail_write_expect_OK($fp, "TO $recipient\n");
69 if ($ret !== true) {
70 return( xl(ErrorConstants::RECIPIENT_NOT_ALLOWED) . " " . $ret );
73 $ret = fgets($fp, 1024); //ignore extra server data
75 $text_out = $message;
77 $text_len = strlen($text_out);
78 phimail_write($fp, "TEXT $text_len\n");
79 $ret = @fgets($fp, 256);
80 if ($ret != "BEGIN\n") {
81 phimail_close($fp);
82 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_FAILED);
85 $ret = phimail_write_expect_OK($fp, $text_out);
86 if ($ret !== true) {
87 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_OK_FAILED);
90 if ($verifyFinalDelivery) {
91 $ret = phimail_write_expect_OK($fp, "SET FINAL 1\n");
92 if ($ret !== true) {
93 return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
95 } else {
96 $ret = phimail_write_expect_OK($fp, "SET FINAL 0\n");
97 if ($ret !== true) {
98 return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
102 phimail_write($fp, "SEND\n");
103 $ret = fgets($fp);
104 phimail_write($fp, "OK\n");
105 phimail_close($fp);
108 if (substr($ret, 5) == "ERROR") {
109 //log the failure
111 EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret);
112 return( xl(ErrorConstants::ERROR_MESSAGE_FILE_SEND_FAILED));
116 * If we get here, the message was successfully sent and the return
117 * value $ret is of the form "QUEUED recipient message-id" which
118 * is suitable for logging.
120 $msg_id = explode(" ", trim($ret), 4);
121 if ($msg_id[0] != "QUEUED" || !isset($msg_id[2])) { //unexpected response
122 $ret = "UNEXPECTED RESPONSE: " . $ret;
123 EventAuditLogger::instance()->newEvent("transmit-message", $reqBy, $_SESSION['authProvider'], 0, $ret);
124 return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
127 EventAuditLogger::instance()->newEvent("transmit-message", $reqBy, $_SESSION['authProvider'], 1, $ret);
128 $adodb = $GLOBALS['adodb']['db'];
129 $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
130 "VALUES ('S', ?, ?, ?, 'S', NOW(), ?)";
131 $res = @sqlStatementNoLog($sql, array($msg_id[2],$phimail_username,$recipient,$reqID));
133 return("SUCCESS");
137 * Connect to a phiMail Direct Messaging server and transmit
138 * a CCD document to the specified recipient. If the message is accepted by the
139 * server, the script will return "SUCCESS", otherwise it will return an error msg.
140 * @param number The patient pid that we are sending a CCDA doc for
141 * @param string ccd the data to transmit, a CCDA document is assumed
142 * @param string recipient the Direct Address of the recipient
143 * @param string requested_by user | patient
144 * @param string The format that the document is in (pdf, xml, html)
145 * @param string The message body the clinician wants to send
146 * @param string The filename to use as the name of the attachment (must included file extension as part of filename)
147 * @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.
148 * @return string result of operation
150 function transmitCCD($pid, $ccd_out, $recipient, $requested_by, $xml_type = "CCD", $format_type = 'xml', $message = '', $filename = '', $verifyFinalDelivery = false): string
152 //get patient name in Last_First format (used for CCDA filename) and
153 //First Last for the message text.
154 $patientData = getPatientPID(array("pid" => $pid));
155 if (empty($patientData)) { // shouldn't ever happen but we need to check anyways
156 return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
158 $patientName2 = "";
159 $att_filename = "";
161 // TODO: do we want to throw an error if we can't get patient data? Probably.
163 if (!empty($patientData[0]['lname'])) {
164 $patientName2 = trim($patientData[0]['fname'] . " " . $patientData[0]['lname']);
167 if (!empty($filename)) {
168 // if we have a filename from our database, we want to send that
169 $att_filename = $filename;
170 $extension = ""; // no extension needed
171 } else if (!empty($patientName2)) {
172 //spaces are the argument delimiter for the phiMail API calls and must be removed
173 // CCDA format requires patient name in last, first format
174 $att_filename = str_replace(" ", "_", $xml_type . "_" . $patientData[0]['lname']
175 . "_" . $patientData[0]['fname']);
176 $extension = "." . $format_type;
179 $config_err = xl(ErrorConstants::MESSAGING_DISABLED) . " " . ErrorConstants::ERROR_CODE_ABBREVIATION . ":";
180 if ($GLOBALS['phimail_enable'] == false) {
181 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGING_DISABLED);
184 $fp = phimail_connect($err);
185 if ($fp === false) {
186 return("$config_err $err");
189 $phimail_username = $GLOBALS['phimail_username'];
190 $cryptoGen = new CryptoGen();
191 $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
192 $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
193 if ($ret !== true) {
194 return("$config_err " . ErrorConstants::ERROR_CODE_AUTH_FAILED);
197 $ret = phimail_write_expect_OK($fp, "TO $recipient\n");
198 if ($ret !== true) {
199 return( xl(ErrorConstants::RECIPIENT_NOT_ALLOWED) . " " . $ret );
202 $ret = fgets($fp, 1024); //ignore extra server data
204 // add whatever the clinican added as a message to be sent.
205 if (is_string($message) && trim($message) != "") {
206 $text_out = $message . "\n";
209 if ($requested_by == "patient") {
210 $text_out .= xl("Delivery of the attached clinical document was requested by the patient") .
211 ($patientName2 == "" ? "." : ", " . $patientName2 . ".");
212 } else {
213 $text_out .= xl("A clinical document is attached") .
214 ($patientName2 == "" ? "." : " " . xl("for patient") . " " . $patientName2 . ".");
218 $text_len = strlen($text_out);
219 phimail_write($fp, "TEXT $text_len\n");
220 $ret = @fgets($fp, 256);
221 if ($ret != "BEGIN\n") {
222 phimail_close($fp);
223 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_FAILED);
226 $ret = phimail_write_expect_OK($fp, $text_out);
227 if ($ret !== true) {
228 return("$config_err " . ErrorConstants::ERROR_CODE_MESSAGE_BEGIN_OK_FAILED);
231 // MU2 CareCoordination added the need to send CCDAs formatted as html,pdf, or xml
232 if ($format_type == 'html') {
233 $add_type = "TEXT";
234 } else if ($format_type == 'pdf') {
235 $add_type = "RAW";
236 } else if ($format_type == 'xml') {
237 $add_type = $xml_type == "CCR" ? "CCR" : "CDA";
238 } else {
239 // unsupported format
240 return ("$config_err " . ErrorConstants::ERROR_CODE_INVALID_FORMAT_TYPE);
243 $ccd_len = strlen($ccd_out);
245 phimail_write($fp, "ADD " . $add_type . " " . $ccd_len . " " . $att_filename . $extension . "\n");
246 $ret = fgets($fp, 256);
247 if ($ret != "BEGIN\n") {
248 phimail_close($fp);
249 return("$config_err " . ErrorConstants::ERROR_CODE_ADD_FILE_FAILED);
252 $ret = phimail_write_expect_OK($fp, $ccd_out);
253 if ($ret !== true) {
254 return("$config_err " . ErrorConstants::ERROR_CODE_ADD_FILE_CONFIRM_FAILED);
257 if ($verifyFinalDelivery) {
258 $ret = phimail_write_expect_OK($fp, "SET FINAL 1\n");
259 if ($ret !== true) {
260 return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
262 } else {
263 $ret = phimail_write_expect_OK($fp, "SET FINAL 0\n");
264 if ($ret !== true) {
265 return( xl(ErrorConstants::ERROR_MESSAGE_SET_DISPOSITION_NOTIFICATION_FAILED) . " " . $ret );
269 phimail_write($fp, "SEND\n");
270 $ret = fgets($fp);
271 phimail_write($fp, "OK\n");
272 phimail_close($fp);
274 if ($requested_by == "patient") {
275 $reqBy = "portal-user";
276 $sql = "SELECT id FROM users WHERE username='portal-user'";
277 if (
278 ($r = sqlStatementNoLog($sql)) === false ||
279 ($u = sqlFetchArray($r)) === false
281 $reqID = 1; //default if we don't have a service user
282 } else {
283 $reqID = $u['id'];
285 } else {
286 $reqBy = $_SESSION['authUser'];
287 $reqID = $_SESSION['authUserID'];
290 if (substr($ret, 5) == "ERROR") {
291 //log the failure
293 EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret, $pid);
294 return( xl(ErrorConstants::ERROR_MESSAGE_FILE_SEND_FAILED));
298 * If we get here, the message was successfully sent and the return
299 * value $ret is of the form "QUEUED recipient message-id" which
300 * is suitable for logging.
302 $msg_id = explode(" ", trim($ret), 4);
303 if ($msg_id[0] != "QUEUED" || !isset($msg_id[2])) { //unexpected response
304 $ret = "UNEXPECTED RESPONSE: " . $ret;
305 EventAuditLogger::instance()->newEvent("transmit-ccd", $reqBy, $_SESSION['authProvider'], 0, $ret, $pid);
306 return( xl(ErrorConstants::ERROR_MESSAGE_UNEXPECTED_RESPONSE));
309 EventAuditLogger::instance()->newEvent("transmit-" . $xml_type, $reqBy, $_SESSION['authProvider'], 1, $ret, $pid);
310 $adodb = $GLOBALS['adodb']['db'];
311 $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,patient_id,user_id) " .
312 "VALUES ('S', ?, ?, ?, 'S', NOW(), ?, ?)";
313 $res = @sqlStatementNoLog($sql, array($msg_id[2],$phimail_username,$recipient,$pid,$reqID));
315 return("SUCCESS");