3 * Background receive function for phiMail Direct Messaging service.
5 * This script is called by the background service manager
6 * at /library/ajax/execute_background_services.php
8 * Copyright (C) 2013 EMR Direct <http://www.emrdirect.com/>
10 * LICENSE: This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
22 * @author EMR Direct <http://www.emrdirect.com/>
23 * @link http://www.open-emr.org
26 require_once(dirname(__FILE__) . "/log.inc");
27 require_once(dirname(__FILE__) . "/sql.inc");
28 require_once(dirname(__FILE__) . "/pnotes.inc");
29 require_once(dirname(__FILE__) . "/../controllers/C_Document.class.php");
30 require_once(dirname(__FILE__) . "/documents.php");
31 require_once(dirname(__FILE__) . "/gprelations.inc.php");
34 * Connect to a phiMail Direct Messaging server
37 function phimail_connect(&$phimail_error) {
39 if ($GLOBALS['phimail_enable'] == false) {
40 $phimail_error = 'C1';
41 return false; //for safety
44 $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
45 $phimail_username = $GLOBALS['phimail_username'];
46 $phimail_password = $GLOBALS['phimail_password'];
47 $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
48 . '/documents/phimail_server_pem/phimail_server.pem';
49 if (!file_exists($phimail_cafile)) $phimail_cafile='';
51 $phimail_secure = true;
52 switch ($phimail_server['scheme']) {
54 case "http": $server = "tcp://".$phimail_server['host'];
55 $phimail_secure = false;
57 case "https": $server = "ssl://" . $phimail_server['host']
58 . ':' . $phimail_server['port'];
62 case "tls": $server = $GLOBALS['phimail_server_address'];
64 default: $phimail_error = 'C2';
67 if ($phimail_secure) {
68 $context = stream_context_create();
69 if ($phimail_cafile != '' &&
70 (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
71 !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))) {
72 $phimail_error = 'C3';
77 while ($socket_tries < 3 && !$fp) {
79 $fp = @stream_socket_client($server, $err1, $err2, 10,
80 STREAM_CLIENT_CONNECT, $context);
83 if ($err1 == '111') $err2 = xl('Server may be offline');
84 if ($err2 == '') $err2 = xl('Connection error');
85 $phimail_error = "C4 $err1 ($err2)";
88 $fp = @fsockopen($server,$phimail_server['port']);
94 * Connect to a phiMail Direct Messaging server and check for any incoming status
95 * messages related to previously transmitted messages or any new messages received.
98 function phimail_check() {
99 $fp = phimail_connect($err);
101 phimail_logit(0,xl('could not connect to server').' '.$err);
104 $phimail_username = $GLOBALS['phimail_username'];
105 $phimail_password = $GLOBALS['phimail_password'];
107 $ret = phimail_write_expect_OK($fp,"AUTH $phimail_username $phimail_password\n");
109 phimail_logit(0, "authentication error " . $ret);
113 if(!($notifyUsername = $GLOBALS['phimail_notify'])) $notifyUsername='admin'; //fallback
116 phimail_write($fp,"CHECK\n");
119 if($ret=="NONE\n") { //nothing to process
121 phimail_logit(1,"message check completed");
124 else if(substr($ret,0,6)=="STATUS") {
125 //Format STATUS message-id status-code [additional-information]
126 $val=explode(" ",trim($ret),4);
127 $sql='SELECT * from direct_message_log WHERE msg_id = ?';
128 $res = sqlStatementNoLog($sql,array($val[1]));
129 if ($res===FALSE) { //database problem
131 phimail_logit(0,"database problem");
134 if (($msg=sqlFetchArray($res))===FALSE) {
135 //no match, so log it and move on (should never happen)
136 phimail_logit(0,"NO MATCH: ".$ret);
137 $ret = phimail_write_expect_OK($fp,"OK\n");
138 if($ret!==TRUE) return; else continue;
141 //if we get here, $msg contains the matching outgoing message record
142 if($val[2]=='failed') {
145 } else if ($val[2]=='dispatched') {
149 //unrecognized status, log it and move on (should never happen)
150 $ret = "UNKNOWN STATUS: ".$ret;
155 phimail_logit($success,$ret,$msg['patient_id']);
157 if (!isset($val[3])) $val[3]="";
158 $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
159 $res = sqlStatementNoLog($sql,array($status,$val[3],$val[1]));
160 if ($res===FALSE) { //database problem
162 phimail_logit(0,"database problem updating: ".$val[1]);
167 //notify local user of failure
168 $sql = "SELECT username FROM users WHERE id = ?";
169 $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
170 $fail_user = ($res2 === FALSE || ($user_row = sqlFetchArray($res2)) === FALSE) ?
171 xl('unknown (see log)') : $user_row['username'];
172 $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
173 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
174 phimail_notify( xl('Direct Messaging Send Failure.'), $fail_notice);
175 $pnote_id = addPnote($msg['patient_id'],
176 xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
177 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
180 //done with this status message
181 $ret = phimail_write_expect_OK($fp,"OK\n");
189 else if(substr($ret,0,4)=="MAIL") {
191 $val = explode(" ",trim($ret),5); // MAIL recipient sender #attachments msg-id
197 //request main message
198 $ret2 = phimail_write_expect_OK($fp,"SHOW 0\n");
204 //get message headers
206 while (($next_hdr = fgets($fp,1024)) != "\n")
209 $mime_type=fgets($fp,512);
210 $mime_info=explode(";",$mime_type);
211 $mime_type_main=strtolower($mime_info[0]);
213 //get main message body
214 $body_len=fgets($fp,256);
215 $body=phimail_read_blob($fp,$body_len);
221 $att2=fgets($fp,256);
222 if($att2!=$att) { //safety for mismatch on attachments
227 //get attachment info
229 for ($attnum=0;$attnum<$att;$attnum++) {
230 if( ($attinfo[$attnum]['name']=fgets($fp,1024)) === FALSE
231 || ($attinfo[$attnum]['mime']=fgets($fp,1024)) === FALSE
232 || ($attinfo[$attnum]['desc']=fgets($fp,1024)) === FALSE) {
239 //main part gets stored as document if not plain text content
240 //(if plain text it will be the body of the final pnote)
241 $all_doc_ids = array();
244 if ($mime_type_main != "text/plain") {
245 $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
246 $doc_id = phimail_store($name,$mime_type_main,$body);
251 $idnum=$doc_id['doc_id'];
252 $all_doc_ids[] = $idnum;
254 $url=substr($url,strrpos($url,"/")+1);
255 $att_detail = "\n" . xl ("Document") . " $idnum (\"$url\"; $mime_type_main; " .
256 filesize($body) . " bytes) Main message body";
259 //download and store attachments
260 for($attnum=0;$attnum<$att;$attnum++) {
261 $ret2 = phimail_write_expect_OK($fp,"SHOW " . ($attnum+1) . "\n");
267 //we can ignore next two lines (repeat of name and mime-type)
268 if ( ($a1=fgets($fp,512))===FALSE || ($a2=fgets($fp,512))===FALSE ) {
273 $att_len = fgets($fp,256); //length of file
274 $attdata = phimail_read_blob($fp,$att_len);
275 if ($attdata===FALSE) {
279 $attinfo[$attnum]['file']=$attdata;
281 $req_name = trim($attinfo[$attnum]['name']);
282 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
283 $attinfo[$attnum]['mime'] = explode(";",trim($attinfo[$attnum]['mime']));
284 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
285 $att_doc_id = phimail_store($req_name, $attmime, $attdata);
290 $attinfo[$attnum]['doc_id']=$att_doc_id;
291 $idnum=$att_doc_id['doc_id'];
292 $all_doc_ids[] = $idnum;
293 $url=$att_doc_id['url'];
294 $url=substr($url,strrpos($url,"/")+1);
295 $att_detail = $att_detail . "\n" . xl ("Document") . " $idnum (\"$url\"; $attmime; " .
296 filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
299 if ($att_detail != "")
300 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
302 $ret2 = phimail_write_expect_OK($fp,"DONE\n"); //we'll check for failure after logging.
304 //logging only after succesful download, storage, and acknowledgement of message
305 $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
306 "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
307 $res = sqlStatementNoLog($sql,array($msg_id,$sender,$recipient,phimail_service_userID()));
309 phimail_logit(1,$ret);
311 //alert appointed user about new message
312 switch($mime_type_main) {
315 $body_text = @file_get_contents($body); //this was not uploaded as a document
317 $pnote_id = addPnote(0, xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
318 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
322 $note = xl("Direct Message Received.") . "\n$hdrs\n"
323 . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
324 $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
329 foreach ($all_doc_ids as $doc_id) setGpRelation(1, $doc_id, 6, $pnote_id);
337 else { //unrecognized or FAIL response
338 phimail_logit(0, "problem checking messages " . $ret);
348 function phimail_write($fp,$text) {
353 function phimail_write_expect_OK($fp,$text) {
354 phimail_write($fp,$text);
355 $ret = fgets($fp,256);
356 if($ret!="OK\n") { //unexpected error
363 function phimail_close($fp) {
369 function phimail_logit($success,$text,$pid=0,$event="direct-message-check") {
370 newEvent($event,"phimail-service",0,$success,$text,$pid);
374 * Read a blob of data into a local temporary file
375 * @param $len number of bytes to read
376 * @return the temp filename, or FALSE if failure
378 function phimail_read_blob($fp,$len) {
380 $fpath=$GLOBALS['temporary_files_dir'];
381 if(!@file_exists($fpath)) {
384 $name = uniqid("direct-");
385 $fn = $fpath . "/" . $name . ".dat";
387 while(file_exists($fn)) {
388 $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
391 $ff = @fopen ($fn, "w");
392 if(!$ff) return FALSE;
396 while (!feof($fp) && $bytes_left>0) {
397 if ($bytes_left < $chunk_size ) $chunk_size = $bytes_left;
398 $chunk = fread($fp,$chunk_size);
399 if($chunk===FALSE || @fwrite($ff,$chunk)===FALSE) {
404 $bytes_left -= strlen($chunk);
411 * Return a suitable filename extension based on MIME-type
412 * (very limited, default is .dat)
414 function phimail_extension($mime) {
415 $m=explode("/",$mime);
431 function phimail_service_userID($name='phimail-service') {
432 $sql = "SELECT id FROM users WHERE username=?";
433 if (($r = sqlStatementNoLog($sql,array($name))) === FALSE ||
434 ($u = sqlFetchArray($r)) === FALSE) {
435 $user=1; //default if we don't have a service user
444 * Registers an attachment or non-text message file using the existing Document structure
445 * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
447 function phimail_store($name,$mime_type,$fn) {
449 // Collect phimail user id
450 $user = phimail_service_userID();
452 // Import the document
453 $return = addNewDocument($name,$mime_type,$fn,0,filesize($fn),$user,'direct');
455 // Remove the temporary file
463 * Send an error notification or other alert to the notification address specified in globals.
464 * (notification email function modified from interface/drugs/dispense_drug.php)
465 * @return true if notification successfully sent, false otherwise
467 function phimail_notify($subj,$body) {
468 $recipient = $GLOBALS['practice_return_email_path'];
469 if (empty($recipient)) return false;
470 $mail = new PHPMailer();
471 $mail->From = $recipient;
472 $mail->FromName = 'phiMail Gateway';
474 $mail->Host = "localhost";
475 $mail->Mailer = "mail";
477 $mail->Subject = $subj;
478 $mail->AddAddress($recipient);
479 return ($mail->Send());