fix provided by mdsupport
[openemr.git] / library / direct_message_check.inc
blob15f435fcb3d8d2cc83b43c0fd3aadbbfdcebf664
1 <?php
2 /**
3  * Background receive function for phiMail Direct Messaging service.
4  *
5  * This script is called by the background service manager
6  * at /library/ajax/execute_background_services.php
7  *
8  * Copyright (C) 2013 EMR Direct <http://www.emrdirect.com/>
9  *
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>;.
20  *
21  * @package OpenEMR
22  * @author  EMR Direct <http://www.emrdirect.com/>
23  * @link    http://www.open-emr.org
24  */
26 require_once(dirname(__FILE__) . "/log.inc");
27 require_once(dirname(__FILE__) . "/pnotes.inc");
28 require_once(dirname(__FILE__) . "/documents.php");
29 require_once(dirname(__FILE__) . "/gprelations.inc.php");
31 /**
32  * Connect to a phiMail Direct Messaging server
33  */
35 function phimail_connect(&$phimail_error)
38     if ($GLOBALS['phimail_enable'] == false) {
39         $phimail_error = 'C1';
40         return false; //for safety
41     }
43     $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
44     $phimail_username = $GLOBALS['phimail_username'];
45     $phimail_password = $GLOBALS['phimail_password'];
46     $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
47       . '/documents/phimail_server_pem/phimail_server.pem';
48     if (!file_exists($phimail_cafile)) {
49         $phimail_cafile='';
50     }
52     $phimail_secure = true;
53     switch ($phimail_server['scheme']) {
54         case "tcp":
55         case "http":
56             $server = "tcp://".$phimail_server['host'];
57               $phimail_secure = false;
58             break;
59         case "https":
60             $server = "ssl://" . $phimail_server['host']
61                . ':' . $phimail_server['port'];
62             break;
63         case "ssl":
64         case "sslv3":
65         case "tls":
66             $server = $GLOBALS['phimail_server_address'];
67             break;
68         default:
69             $phimail_error = 'C2';
70             return false;
71     }
73     if ($phimail_secure) {
74         $context = stream_context_create();
75         if ($phimail_cafile != '' &&
76             (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
77              !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))) {
78             $phimail_error = 'C3';
79             return false;
80         }
82         $socket_tries = 0;
83         $fp = false;
84         while ($socket_tries < 3 && !$fp) {
85             $socket_tries++;
86             $fp = @stream_socket_client(
87                 $server,
88                 $err1,
89                 $err2,
90                 10,
91                 STREAM_CLIENT_CONNECT,
92                 $context
93             );
94         }
96         if (!$fp) {
97             if ($err1 == '111') {
98                 $err2 = xl('Server may be offline');
99             }
101             if ($err2 == '') {
102                 $err2 = xl('Connection error');
103             }
105             $phimail_error = "C4 $err1 ($err2)";
106         }
107     } else {
108         $fp = @fsockopen($server, $phimail_server['port']);
109     }
111     return $fp;
115  * Connect to a phiMail Direct Messaging server and check for any incoming status
116  * messages related to previously transmitted messages or any new messages received.
117  */
119 function phimail_check()
121     $fp = phimail_connect($err);
122     if ($fp===false) {
123         phimail_logit(0, xl('could not connect to server').' '.$err);
124         return;
125     }
127     $phimail_username = $GLOBALS['phimail_username'];
128     $phimail_password = $GLOBALS['phimail_password'];
130     $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
131     if ($ret!==true) {
132         phimail_logit(0, "authentication error " . $ret);
133         return;
134     }
136     if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
137         $notifyUsername='admin'; //fallback
138     }
140     while (1) {
141         phimail_write($fp, "CHECK\n");
142         $ret=fgets($fp, 512);
144         if ($ret=="NONE\n") { //nothing to process
145             phimail_close($fp);
146             phimail_logit(1, "message check completed");
147             return;
148         } else if (substr($ret, 0, 6)=="STATUS") {
149          //Format STATUS message-id status-code [additional-information]
150             $val=explode(" ", trim($ret), 4);
151             $sql='SELECT * from direct_message_log WHERE msg_id = ?';
152             $res = sqlStatementNoLog($sql, array($val[1]));
153             if ($res===false) { //database problem
154                 phimail_close($fp);
155                 phimail_logit(0, "database problem");
156                 return;
157             }
159             if (($msg=sqlFetchArray($res))===false) {
160                 //no match, so log it and move on (should never happen)
161                 phimail_logit(0, "NO MATCH: ".$ret);
162                 $ret = phimail_write_expect_OK($fp, "OK\n");
163                 if ($ret!==true) {
164                     return;
165                 } else {
166                     continue;
167                 }
168             }
170             //if we get here, $msg contains the matching outgoing message record
171             if ($val[2]=='failed') {
172                 $success=0;
173                 $status='F';
174             } else if ($val[2]=='dispatched') {
175                 $success=1;
176                 $status='D';
177             } else {
178             //unrecognized status, log it and move on (should never happen)
179                 $ret = "UNKNOWN STATUS: ".$ret;
180                 $success=0;
181                 $status='U';
182             }
184             phimail_logit($success, $ret, $msg['patient_id']);
186             if (!isset($val[3])) {
187                 $val[3]="";
188             }
190             $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
191             $res = sqlStatementNoLog($sql, array($status,$val[3],$val[1]));
192             if ($res===false) { //database problem
193                 phimail_close($fp);
194                 phimail_logit(0, "database problem updating: ".$val[1]);
195                 return;
196             }
198             if (!$success) {
199                    //notify local user of failure
200                    $sql = "SELECT username FROM users WHERE id = ?";
201                    $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
202                    $fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
203                 xl('unknown (see log)') : $user_row['username'];
204                    $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
205                 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
206                 phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
207                    $pnote_id = addPnote(
208                        $msg['patient_id'],
209                        xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
210                        0,
211                        1,
212                        "Unassigned",
213                        $notifyUsername,
214                        "",
215                        "New",
216                        "phimail-service"
217                    );
218             }
220          //done with this status message
221             $ret = phimail_write_expect_OK($fp, "OK\n");
222             if ($ret!==true) {
223                    phimail_close($fp);
224                 return;
225             }
226         } else if (substr($ret, 0, 4)=="MAIL") {
227             $val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
228             $recipient=$val[1];
229             $sender=$val[2];
230             $att=(int)$val[3];
231             $msg_id=$val[4];
233             //request main message
234             $ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
235             if ($ret2!==true) {
236                 phimail_close($fp);
237                 return;
238             }
240         //get message headers
241             $hdrs="";
242             while (($next_hdr = fgets($fp, 1024)) != "\n") {
243                 $hdrs .= $next_hdr;
244             }
246             $mime_type=fgets($fp, 512);
247             $mime_info=explode(";", $mime_type);
248             $mime_type_main=strtolower($mime_info[0]);
250         //get main message body
251             $body_len=fgets($fp, 256);
252             $body=phimail_read_blob($fp, $body_len);
253             if ($body===false) {
254                    phimail_close($fp);
255                    return;
256             }
258             $att2=fgets($fp, 256);
259             if ($att2!=$att) { //safety for mismatch on attachments
260                    phimail_close($fp);
261                    return;
262             }
264         //get attachment info
265             if ($att>0) {
266                 for ($attnum=0; $attnum<$att; $attnum++) {
267                     if (($attinfo[$attnum]['name']=fgets($fp, 1024)) === false
268                     || ($attinfo[$attnum]['mime']=fgets($fp, 1024)) === false
269                     || ($attinfo[$attnum]['desc']=fgets($fp, 1024)) === false) {
270                              phimail_close($fp);
271                              return;
272                     }
273                 }
274             }
276         //main part gets stored as document if not plain text content
277         //(if plain text it will be the body of the final pnote)
278             $all_doc_ids = array();
279             $doc_id=0;
280             $att_detail="";
281             if ($mime_type_main != "text/plain") {
282                    $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
283                    $doc_id = phimail_store($name, $mime_type_main, $body);
284                 if (!$doc_id) {
285                     phimail_close($fp);
286                     return;
287                 }
289                    $idnum=$doc_id['doc_id'];
290                    $all_doc_ids[] = $idnum;
291                    $url=$doc_id['url'];
292                    $url=substr($url, strrpos($url, "/")+1);
293                    $att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
294                   filesize($body) . " bytes) Main message body";
295             }
297         //download and store attachments
298             for ($attnum=0; $attnum<$att; $attnum++) {
299                 $ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum+1) . "\n");
300                 if ($ret2!==true) {
301                     phimail_close($fp);
302                     return;
303                 }
305                //we can ignore next two lines (repeat of name and mime-type)
306                 if (($a1=fgets($fp, 512))===false || ($a2=fgets($fp, 512))===false) {
307                     phimail_close($fp);
308                     return;
309                 }
311                 $att_len = fgets($fp, 256); //length of file
312                 $attdata = phimail_read_blob($fp, $att_len);
313                 if ($attdata===false) {
314                     phimail_close($fp);
315                     return;
316                 }
318                 $attinfo[$attnum]['file']=$attdata;
320                 $req_name = trim($attinfo[$attnum]['name']);
321                 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
322                 $attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
323                 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
324                 $att_doc_id = phimail_store($req_name, $attmime, $attdata);
325                 if (!$att_doc_id) {
326                     phimail_close($fp);
327                     return;
328                 }
330                 $attinfo[$attnum]['doc_id']=$att_doc_id;
331                 $idnum=$att_doc_id['doc_id'];
332                 $all_doc_ids[] = $idnum;
333                 $url=$att_doc_id['url'];
334                 $url=substr($url, strrpos($url, "/")+1);
335                 $att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
336                 filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
337             }
339             if ($att_detail != "") {
340                 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
341             }
343             $ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
345         //logging only after succesful download, storage, and acknowledgement of message
346             $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
347             "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
348             $res = sqlStatementNoLog($sql, array($msg_id,$sender,$recipient,phimail_service_userID()));
350             phimail_logit(1, $ret);
352         //alert appointed user about new message
353             switch ($mime_type_main) {
354                 case "text/plain":
355                     $body_text = @file_get_contents($body); //this was not uploaded as a document
356                     unlink($body);
357                     $pnote_id = addPnote(
358                         0,
359                         xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
360                         0,
361                         1,
362                         "Unassigned",
363                         $notifyUsername,
364                         "",
365                         "New",
366                         "phimail-service"
367                     );
368                     break;
370                 default:
371                     $note = xl("Direct Message Received.") . "\n$hdrs\n"
372                     . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
373                     $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
374                     break;
375             }
377             foreach ($all_doc_ids as $doc_id) {
378                 setGpRelation(1, $doc_id, 6, $pnote_id);
379             }
381             if ($ret2!==true) {
382                    phimail_close();
383                    return;
384             }
385         } else { //unrecognized or FAIL response
386             phimail_logit(0, "problem checking messages " . $ret);
387             phimail_close($fp);
388             return;
389         }
390     }
394  * Helper functions
395  */
396 function phimail_write($fp, $text)
398     fwrite($fp, $text);
399     fflush($fp);
402 function phimail_write_expect_OK($fp, $text)
404     phimail_write($fp, $text);
405     $ret = fgets($fp, 256);
406     if ($ret!="OK\n") { //unexpected error
407         phimail_close($fp);
408         return $ret;
409     }
411     return true;
414 function phimail_close($fp)
416     fwrite($fp, "BYE\n");
417     fflush($fp);
418     fclose($fp);
421 function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
423     newEvent($event, "phimail-service", 0, $success, $text, $pid);
427  * Read a blob of data into a local temporary file
428  * @param $len number of bytes to read
429  * @return the temp filename, or FALSE if failure
430  */
431 function phimail_read_blob($fp, $len)
434     $fpath=$GLOBALS['temporary_files_dir'];
435     if (!@file_exists($fpath)) {
436         return false;
437     }
439     $name = uniqid("direct-");
440     $fn = $fpath . "/" . $name . ".dat";
441     $dup = 1;
442     while (file_exists($fn)) {
443         $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
444     }
446     $ff = @fopen($fn, "w");
447     if (!$ff) {
448         return false;
449     }
451     $bytes_left=$len;
452     $chunk_size=1024;
453     while (!feof($fp) && $bytes_left>0) {
454         if ($bytes_left < $chunk_size) {
455             $chunk_size = $bytes_left;
456         }
458         $chunk = fread($fp, $chunk_size);
459         if ($chunk===false || @fwrite($ff, $chunk)===false) {
460             @fclose($ff);
461             @unlink($fn);
462             return false;
463         }
465         $bytes_left -= strlen($chunk);
466     }
468     @fclose($ff);
469     return($fn);
473  * Return a suitable filename extension based on MIME-type
474  * (very limited, default is .dat)
475  */
476 function phimail_extension($mime)
478     $m=explode("/", $mime);
479     switch ($mime) {
480         case 'text/plain':
481             return (".txt");
482         default:
483     }
485     switch ($m[1]) {
486         case 'html':
487         case 'xml':
488         case 'pdf':
489             return (".".$m[1]);
490         default:
491             return (".dat");
492     }
495 function phimail_service_userID($name = 'phimail-service')
497     $sql = "SELECT id FROM users WHERE username=?";
498     if (($r = sqlStatementNoLog($sql, array($name))) === false ||
499        ($u = sqlFetchArray($r)) === false) {
500         $user=1; //default if we don't have a service user
501     } else {
502         $user = $u['id'];
503     }
505     return ($user);
510  * Registers an attachment or non-text message file using the existing Document structure
511  * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
512  */
513 function phimail_store($name, $mime_type, $fn)
516     // Collect phimail user id
517     $user = phimail_service_userID();
519     // Import the document
520     $return = addNewDocument($name, $mime_type, $fn, 0, filesize($fn), $user, 'direct');
522     // Remove the temporary file
523     @unlink($fn);
525     // Return the result
526     return $return;
530  * Send an error notification or other alert to the notification address specified in globals.
531  * (notification email function modified from interface/drugs/dispense_drug.php)
532  * @return true if notification successfully sent, false otherwise
533  */
534 function phimail_notify($subj, $body)
536     $recipient = $GLOBALS['practice_return_email_path'];
537     if (empty($recipient)) {
538         return false;
539     }
541     $mail = new PHPMailer();
542     $mail->From = $recipient;
543     $mail->FromName = 'phiMail Gateway';
544     $mail->isMail();
545     $mail->Host = "localhost";
546     $mail->Mailer = "mail";
547     $mail->Body = $body;
548     $mail->Subject = $subj;
549     $mail->AddAddress($recipient);
550     return ($mail->Send());