4 * Background receive function for phiMail Direct Messaging service.
6 * This script is called by the background service manager
7 * at /library/ajax/execute_background_services.php
9 * Copyright (C) 2013 EMR Direct <http://www.emrdirect.com/>
11 * LICENSE: This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
23 * @author EMR Direct <http://www.emrdirect.com/>
24 * @link http://www.open-emr.org
27 require_once(dirname(__FILE__) . "/pnotes.inc");
28 require_once(dirname(__FILE__) . "/documents.php");
29 require_once(dirname(__FILE__) . "/gprelations.inc.php");
31 use OpenEMR\Common\Crypto\CryptoGen;
32 use OpenEMR\Common\Logging\EventAuditLogger;
33 use PHPMailer\PHPMailer\PHPMailer;
36 * Connect to a phiMail Direct Messaging server
39 function phimail_connect(&$phimail_error)
42 if ($GLOBALS['phimail_enable'] == false) {
43 $phimail_error = 'C1';
44 return false; //for safety
47 $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
48 $phimail_username = $GLOBALS['phimail_username'];
49 $cryptoGen = new CryptoGen();
50 $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
51 $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
52 . '/documents/phimail_server_pem/phimail_server.pem';
53 if (!file_exists($phimail_cafile)) {
57 $phimail_secure = true;
58 switch ($phimail_server['scheme']) {
61 $server = "tcp://" . $phimail_server['host'];
62 $phimail_secure = false;
65 $server = "ssl://" . $phimail_server['host']
66 . ':' . $phimail_server['port'];
71 $server = $GLOBALS['phimail_server_address'];
74 $phimail_error = 'C2';
78 if ($phimail_secure) {
79 $context = stream_context_create();
81 $phimail_cafile != '' &&
82 (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
83 !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))
85 $phimail_error = 'C3';
91 while ($socket_tries < 3 && !$fp) {
93 $fp = @stream_socket_client(
98 STREAM_CLIENT_CONNECT,
104 if ($err1 == '111') {
105 $err2 = xl('Server may be offline');
109 $err2 = xl('Connection error');
112 $phimail_error = "C4 $err1 ($err2)";
115 $fp = @fsockopen($server, $phimail_server['port']);
122 * Connect to a phiMail Direct Messaging server and check for any incoming status
123 * messages related to previously transmitted messages or any new messages received.
126 function phimail_check()
128 $fp = phimail_connect($err);
130 phimail_logit(0, xl('could not connect to server') . ' ' . $err);
134 $phimail_username = $GLOBALS['phimail_username'];
135 $cryptoGen = new CryptoGen();
136 $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
138 $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
140 phimail_logit(0, "authentication error " . $ret);
144 if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
145 $notifyUsername = 'admin'; //fallback
149 phimail_write($fp, "CHECK\n");
150 $ret = fgets($fp, 512);
152 if ($ret == "NONE\n") { //nothing to process
154 phimail_logit(1, "message check completed");
156 } elseif (substr($ret, 0, 6) == "STATUS") {
157 //Format STATUS message-id status-code [additional-information]
158 $val = explode(" ", trim($ret), 4);
159 $sql = 'SELECT * from direct_message_log WHERE msg_id = ?';
160 $res = sqlStatementNoLog($sql, array($val[1]));
161 if ($res === false) { //database problem
163 phimail_logit(0, "database problem");
167 if (($msg = sqlFetchArray($res)) === false) {
168 //no match, so log it and move on (should never happen)
169 phimail_logit(0, "NO MATCH: " . $ret);
170 $ret = phimail_write_expect_OK($fp, "OK\n");
178 //if we get here, $msg contains the matching outgoing message record
179 if ($val[2] == 'failed') {
182 } elseif ($val[2] == 'dispatched') {
186 //unrecognized status, log it and move on (should never happen)
187 $ret = "UNKNOWN STATUS: " . $ret;
192 phimail_logit($success, $ret, $msg['patient_id']);
194 if (!isset($val[3])) {
198 $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
199 $res = sqlStatementNoLog($sql, array($status,$val[3],$val[1]));
200 if ($res === false) { //database problem
202 phimail_logit(0, "database problem updating: " . $val[1]);
207 //notify local user of failure
208 $sql = "SELECT username FROM users WHERE id = ?";
209 $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
210 $fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
211 xl('unknown (see log)') : $user_row['username'];
212 $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
213 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
214 phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
215 $pnote_id = addPnote(
217 xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
228 //done with this status message
229 $ret = phimail_write_expect_OK($fp, "OK\n");
234 } elseif (substr($ret, 0, 4) == "MAIL") {
235 $val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
236 $recipient = $val[1];
241 //request main message
242 $ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
243 if ($ret2 !== true) {
248 //get message headers
250 while (($next_hdr = fgets($fp, 1024)) != "\n") {
254 $mime_type = fgets($fp, 512);
255 $mime_info = explode(";", $mime_type);
256 $mime_type_main = strtolower($mime_info[0]);
258 //get main message body
259 $body_len = fgets($fp, 256);
260 $body = phimail_read_blob($fp, $body_len);
261 if ($body === false) {
266 $att2 = fgets($fp, 256);
267 if ($att2 != $att) { //safety for mismatch on attachments
272 //get attachment info
274 for ($attnum = 0; $attnum < $att; $attnum++) {
276 ($attinfo[$attnum]['name'] = fgets($fp, 1024)) === false
277 || ($attinfo[$attnum]['mime'] = fgets($fp, 1024)) === false
278 || ($attinfo[$attnum]['desc'] = fgets($fp, 1024)) === false
286 //main part gets stored as document if not plain text content
287 //(if plain text it will be the body of the final pnote)
288 $all_doc_ids = array();
291 if ($mime_type_main != "text/plain") {
292 $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
293 $doc_id = phimail_store($name, $mime_type_main, $body);
299 $idnum = $doc_id['doc_id'];
300 $all_doc_ids[] = $idnum;
301 $url = $doc_id['url'];
302 $url = substr($url, strrpos($url, "/") + 1);
303 $att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
304 filesize($body) . " bytes) Main message body";
307 //download and store attachments
308 for ($attnum = 0; $attnum < $att; $attnum++) {
309 $ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum + 1) . "\n");
310 if ($ret2 !== true) {
315 //we can ignore next two lines (repeat of name and mime-type)
316 if (($a1 = fgets($fp, 512)) === false || ($a2 = fgets($fp, 512)) === false) {
321 $att_len = fgets($fp, 256); //length of file
322 $attdata = phimail_read_blob($fp, $att_len);
323 if ($attdata === false) {
328 $attinfo[$attnum]['file'] = $attdata;
330 $req_name = trim($attinfo[$attnum]['name']);
331 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
332 $attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
333 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
334 $att_doc_id = phimail_store($req_name, $attmime, $attdata);
340 $attinfo[$attnum]['doc_id'] = $att_doc_id;
341 $idnum = $att_doc_id['doc_id'];
342 $all_doc_ids[] = $idnum;
343 $url = $att_doc_id['url'];
344 $url = substr($url, strrpos($url, "/") + 1);
345 $att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
346 filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
349 if ($att_detail != "") {
350 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
353 $ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
355 //logging only after succesful download, storage, and acknowledgement of message
356 $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
357 "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
358 $res = sqlStatementNoLog($sql, array($msg_id,$sender,$recipient,phimail_service_userID()));
360 phimail_logit(1, $ret);
362 //alert appointed user about new message
363 switch ($mime_type_main) {
365 $body_text = @file_get_contents($body); //this was not uploaded as a document
367 $pnote_id = addPnote(
369 xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
381 $note = xl("Direct Message Received.") . "\n$hdrs\n"
382 . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
383 $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
387 foreach ($all_doc_ids as $doc_id) {
388 setGpRelation(1, $doc_id, 6, $pnote_id);
391 if ($ret2 !== true) {
395 } else { //unrecognized or FAIL response
396 phimail_logit(0, "problem checking messages " . $ret);
406 function phimail_write($fp, $text)
412 function phimail_write_expect_OK($fp, $text)
414 phimail_write($fp, $text);
415 $ret = fgets($fp, 256);
416 if ($ret != "OK\n") { //unexpected error
424 function phimail_close($fp)
426 fwrite($fp, "BYE\n");
431 function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
433 EventAuditLogger::instance()->newEvent($event, "phimail-service", 0, $success, $text, $pid);
437 * Read a blob of data into a local temporary file
438 * @param $len number of bytes to read
439 * @return the temp filename, or FALSE if failure
441 function phimail_read_blob($fp, $len)
444 $fpath = $GLOBALS['temporary_files_dir'];
445 if (!@file_exists($fpath)) {
449 $name = uniqid("direct-");
450 $fn = $fpath . "/" . $name . ".dat";
452 while (file_exists($fn)) {
453 $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
456 $ff = @fopen($fn, "w");
463 while (!feof($fp) && $bytes_left > 0) {
464 if ($bytes_left < $chunk_size) {
465 $chunk_size = $bytes_left;
468 $chunk = fread($fp, $chunk_size);
469 if ($chunk === false || @fwrite($ff, $chunk) === false) {
475 $bytes_left -= strlen($chunk);
483 * Return a suitable filename extension based on MIME-type
484 * (very limited, default is .dat)
486 function phimail_extension($mime)
488 $m = explode("/", $mime);
499 return ("." . $m[1]);
505 function phimail_service_userID($name = 'phimail-service')
507 $sql = "SELECT id FROM users WHERE username=?";
509 ($r = sqlStatementNoLog($sql, array($name))) === false ||
510 ($u = sqlFetchArray($r)) === false
512 $user = 1; //default if we don't have a service user
522 * Registers an attachment or non-text message file using the existing Document structure
523 * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
525 function phimail_store($name, $mime_type, $fn)
528 // Collect phimail user id
529 $user = phimail_service_userID();
531 // Import the document
532 $return = addNewDocument($name, $mime_type, $fn, 0, filesize($fn), $user, 'direct');
534 // Remove the temporary file
542 * Send an error notification or other alert to the notification address specified in globals.
543 * (notification email function modified from interface/drugs/dispense_drug.php)
544 * @return true if notification successfully sent, false otherwise
546 function phimail_notify($subj, $body)
548 $recipient = $GLOBALS['practice_return_email_path'];
549 if (empty($recipient)) {
553 $mail = new PHPMailer();
554 $mail->From = $recipient;
555 $mail->FromName = 'phiMail Gateway';
557 $mail->Host = "localhost";
558 $mail->Mailer = "mail";
560 $mail->Subject = $subj;
561 $mail->AddAddress($recipient);
562 return ($mail->Send());