Fully responsive globals.php with vertical menu (#2460)
[openemr.git] / library / direct_message_check.inc
blob711373f9e41e4423b380528ae730985cd85d4d32
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__) . "/pnotes.inc");
27 require_once(dirname(__FILE__) . "/documents.php");
28 require_once(dirname(__FILE__) . "/gprelations.inc.php");
30 use OpenEMR\Common\Crypto\CryptoGen;
31 use OpenEMR\Common\Logging\EventAuditLogger;
32 use PHPMailer\PHPMailer\PHPMailer;
34 /**
35  * Connect to a phiMail Direct Messaging server
36  */
38 function phimail_connect(&$phimail_error)
41     if ($GLOBALS['phimail_enable'] == false) {
42         $phimail_error = 'C1';
43         return false; //for safety
44     }
46     $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
47     $phimail_username = $GLOBALS['phimail_username'];
48     $cryptoGen = new CryptoGen();
49     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
50     $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
51       . '/documents/phimail_server_pem/phimail_server.pem';
52     if (!file_exists($phimail_cafile)) {
53         $phimail_cafile='';
54     }
56     $phimail_secure = true;
57     switch ($phimail_server['scheme']) {
58         case "tcp":
59         case "http":
60             $server = "tcp://".$phimail_server['host'];
61               $phimail_secure = false;
62             break;
63         case "https":
64             $server = "ssl://" . $phimail_server['host']
65                . ':' . $phimail_server['port'];
66             break;
67         case "ssl":
68         case "sslv3":
69         case "tls":
70             $server = $GLOBALS['phimail_server_address'];
71             break;
72         default:
73             $phimail_error = 'C2';
74             return false;
75     }
77     if ($phimail_secure) {
78         $context = stream_context_create();
79         if ($phimail_cafile != '' &&
80             (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
81              !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))) {
82             $phimail_error = 'C3';
83             return false;
84         }
86         $socket_tries = 0;
87         $fp = false;
88         while ($socket_tries < 3 && !$fp) {
89             $socket_tries++;
90             $fp = @stream_socket_client(
91                 $server,
92                 $err1,
93                 $err2,
94                 10,
95                 STREAM_CLIENT_CONNECT,
96                 $context
97             );
98         }
100         if (!$fp) {
101             if ($err1 == '111') {
102                 $err2 = xl('Server may be offline');
103             }
105             if ($err2 == '') {
106                 $err2 = xl('Connection error');
107             }
109             $phimail_error = "C4 $err1 ($err2)";
110         }
111     } else {
112         $fp = @fsockopen($server, $phimail_server['port']);
113     }
115     return $fp;
119  * Connect to a phiMail Direct Messaging server and check for any incoming status
120  * messages related to previously transmitted messages or any new messages received.
121  */
123 function phimail_check()
125     $fp = phimail_connect($err);
126     if ($fp===false) {
127         phimail_logit(0, xl('could not connect to server').' '.$err);
128         return;
129     }
131     $phimail_username = $GLOBALS['phimail_username'];
132     $cryptoGen = new CryptoGen();
133     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
135     $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
136     if ($ret!==true) {
137         phimail_logit(0, "authentication error " . $ret);
138         return;
139     }
141     if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
142         $notifyUsername='admin'; //fallback
143     }
145     while (1) {
146         phimail_write($fp, "CHECK\n");
147         $ret=fgets($fp, 512);
149         if ($ret=="NONE\n") { //nothing to process
150             phimail_close($fp);
151             phimail_logit(1, "message check completed");
152             return;
153         } else if (substr($ret, 0, 6)=="STATUS") {
154          //Format STATUS message-id status-code [additional-information]
155             $val=explode(" ", trim($ret), 4);
156             $sql='SELECT * from direct_message_log WHERE msg_id = ?';
157             $res = sqlStatementNoLog($sql, array($val[1]));
158             if ($res===false) { //database problem
159                 phimail_close($fp);
160                 phimail_logit(0, "database problem");
161                 return;
162             }
164             if (($msg=sqlFetchArray($res))===false) {
165                 //no match, so log it and move on (should never happen)
166                 phimail_logit(0, "NO MATCH: ".$ret);
167                 $ret = phimail_write_expect_OK($fp, "OK\n");
168                 if ($ret!==true) {
169                     return;
170                 } else {
171                     continue;
172                 }
173             }
175             //if we get here, $msg contains the matching outgoing message record
176             if ($val[2]=='failed') {
177                 $success=0;
178                 $status='F';
179             } else if ($val[2]=='dispatched') {
180                 $success=1;
181                 $status='D';
182             } else {
183             //unrecognized status, log it and move on (should never happen)
184                 $ret = "UNKNOWN STATUS: ".$ret;
185                 $success=0;
186                 $status='U';
187             }
189             phimail_logit($success, $ret, $msg['patient_id']);
191             if (!isset($val[3])) {
192                 $val[3]="";
193             }
195             $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
196             $res = sqlStatementNoLog($sql, array($status,$val[3],$val[1]));
197             if ($res===false) { //database problem
198                 phimail_close($fp);
199                 phimail_logit(0, "database problem updating: ".$val[1]);
200                 return;
201             }
203             if (!$success) {
204                    //notify local user of failure
205                    $sql = "SELECT username FROM users WHERE id = ?";
206                    $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
207                    $fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
208                 xl('unknown (see log)') : $user_row['username'];
209                    $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
210                 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
211                 phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
212                    $pnote_id = addPnote(
213                        $msg['patient_id'],
214                        xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
215                        0,
216                        1,
217                        "Unassigned",
218                        $notifyUsername,
219                        "",
220                        "New",
221                        "phimail-service"
222                    );
223             }
225          //done with this status message
226             $ret = phimail_write_expect_OK($fp, "OK\n");
227             if ($ret!==true) {
228                    phimail_close($fp);
229                 return;
230             }
231         } else if (substr($ret, 0, 4)=="MAIL") {
232             $val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
233             $recipient=$val[1];
234             $sender=$val[2];
235             $att=(int)$val[3];
236             $msg_id=$val[4];
238             //request main message
239             $ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
240             if ($ret2!==true) {
241                 phimail_close($fp);
242                 return;
243             }
245         //get message headers
246             $hdrs="";
247             while (($next_hdr = fgets($fp, 1024)) != "\n") {
248                 $hdrs .= $next_hdr;
249             }
251             $mime_type=fgets($fp, 512);
252             $mime_info=explode(";", $mime_type);
253             $mime_type_main=strtolower($mime_info[0]);
255         //get main message body
256             $body_len=fgets($fp, 256);
257             $body=phimail_read_blob($fp, $body_len);
258             if ($body===false) {
259                    phimail_close($fp);
260                    return;
261             }
263             $att2=fgets($fp, 256);
264             if ($att2!=$att) { //safety for mismatch on attachments
265                    phimail_close($fp);
266                    return;
267             }
269         //get attachment info
270             if ($att>0) {
271                 for ($attnum=0; $attnum<$att; $attnum++) {
272                     if (($attinfo[$attnum]['name']=fgets($fp, 1024)) === false
273                     || ($attinfo[$attnum]['mime']=fgets($fp, 1024)) === false
274                     || ($attinfo[$attnum]['desc']=fgets($fp, 1024)) === false) {
275                              phimail_close($fp);
276                              return;
277                     }
278                 }
279             }
281         //main part gets stored as document if not plain text content
282         //(if plain text it will be the body of the final pnote)
283             $all_doc_ids = array();
284             $doc_id=0;
285             $att_detail="";
286             if ($mime_type_main != "text/plain") {
287                    $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
288                    $doc_id = phimail_store($name, $mime_type_main, $body);
289                 if (!$doc_id) {
290                     phimail_close($fp);
291                     return;
292                 }
294                    $idnum=$doc_id['doc_id'];
295                    $all_doc_ids[] = $idnum;
296                    $url=$doc_id['url'];
297                    $url=substr($url, strrpos($url, "/")+1);
298                    $att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
299                   filesize($body) . " bytes) Main message body";
300             }
302         //download and store attachments
303             for ($attnum=0; $attnum<$att; $attnum++) {
304                 $ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum+1) . "\n");
305                 if ($ret2!==true) {
306                     phimail_close($fp);
307                     return;
308                 }
310                //we can ignore next two lines (repeat of name and mime-type)
311                 if (($a1=fgets($fp, 512))===false || ($a2=fgets($fp, 512))===false) {
312                     phimail_close($fp);
313                     return;
314                 }
316                 $att_len = fgets($fp, 256); //length of file
317                 $attdata = phimail_read_blob($fp, $att_len);
318                 if ($attdata===false) {
319                     phimail_close($fp);
320                     return;
321                 }
323                 $attinfo[$attnum]['file']=$attdata;
325                 $req_name = trim($attinfo[$attnum]['name']);
326                 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
327                 $attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
328                 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
329                 $att_doc_id = phimail_store($req_name, $attmime, $attdata);
330                 if (!$att_doc_id) {
331                     phimail_close($fp);
332                     return;
333                 }
335                 $attinfo[$attnum]['doc_id']=$att_doc_id;
336                 $idnum=$att_doc_id['doc_id'];
337                 $all_doc_ids[] = $idnum;
338                 $url=$att_doc_id['url'];
339                 $url=substr($url, strrpos($url, "/")+1);
340                 $att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
341                 filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
342             }
344             if ($att_detail != "") {
345                 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
346             }
348             $ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
350         //logging only after succesful download, storage, and acknowledgement of message
351             $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
352             "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
353             $res = sqlStatementNoLog($sql, array($msg_id,$sender,$recipient,phimail_service_userID()));
355             phimail_logit(1, $ret);
357         //alert appointed user about new message
358             switch ($mime_type_main) {
359                 case "text/plain":
360                     $body_text = @file_get_contents($body); //this was not uploaded as a document
361                     unlink($body);
362                     $pnote_id = addPnote(
363                         0,
364                         xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
365                         0,
366                         1,
367                         "Unassigned",
368                         $notifyUsername,
369                         "",
370                         "New",
371                         "phimail-service"
372                     );
373                     break;
375                 default:
376                     $note = xl("Direct Message Received.") . "\n$hdrs\n"
377                     . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
378                     $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
379                     break;
380             }
382             foreach ($all_doc_ids as $doc_id) {
383                 setGpRelation(1, $doc_id, 6, $pnote_id);
384             }
386             if ($ret2!==true) {
387                    phimail_close();
388                    return;
389             }
390         } else { //unrecognized or FAIL response
391             phimail_logit(0, "problem checking messages " . $ret);
392             phimail_close($fp);
393             return;
394         }
395     }
399  * Helper functions
400  */
401 function phimail_write($fp, $text)
403     fwrite($fp, $text);
404     fflush($fp);
407 function phimail_write_expect_OK($fp, $text)
409     phimail_write($fp, $text);
410     $ret = fgets($fp, 256);
411     if ($ret!="OK\n") { //unexpected error
412         phimail_close($fp);
413         return $ret;
414     }
416     return true;
419 function phimail_close($fp)
421     fwrite($fp, "BYE\n");
422     fflush($fp);
423     fclose($fp);
426 function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
428     EventAuditLogger::instance()->newEvent($event, "phimail-service", 0, $success, $text, $pid);
432  * Read a blob of data into a local temporary file
433  * @param $len number of bytes to read
434  * @return the temp filename, or FALSE if failure
435  */
436 function phimail_read_blob($fp, $len)
439     $fpath=$GLOBALS['temporary_files_dir'];
440     if (!@file_exists($fpath)) {
441         return false;
442     }
444     $name = uniqid("direct-");
445     $fn = $fpath . "/" . $name . ".dat";
446     $dup = 1;
447     while (file_exists($fn)) {
448         $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
449     }
451     $ff = @fopen($fn, "w");
452     if (!$ff) {
453         return false;
454     }
456     $bytes_left=$len;
457     $chunk_size=1024;
458     while (!feof($fp) && $bytes_left>0) {
459         if ($bytes_left < $chunk_size) {
460             $chunk_size = $bytes_left;
461         }
463         $chunk = fread($fp, $chunk_size);
464         if ($chunk===false || @fwrite($ff, $chunk)===false) {
465             @fclose($ff);
466             @unlink($fn);
467             return false;
468         }
470         $bytes_left -= strlen($chunk);
471     }
473     @fclose($ff);
474     return($fn);
478  * Return a suitable filename extension based on MIME-type
479  * (very limited, default is .dat)
480  */
481 function phimail_extension($mime)
483     $m=explode("/", $mime);
484     switch ($mime) {
485         case 'text/plain':
486             return (".txt");
487         default:
488     }
490     switch ($m[1]) {
491         case 'html':
492         case 'xml':
493         case 'pdf':
494             return (".".$m[1]);
495         default:
496             return (".dat");
497     }
500 function phimail_service_userID($name = 'phimail-service')
502     $sql = "SELECT id FROM users WHERE username=?";
503     if (($r = sqlStatementNoLog($sql, array($name))) === false ||
504        ($u = sqlFetchArray($r)) === false) {
505         $user=1; //default if we don't have a service user
506     } else {
507         $user = $u['id'];
508     }
510     return ($user);
515  * Registers an attachment or non-text message file using the existing Document structure
516  * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
517  */
518 function phimail_store($name, $mime_type, $fn)
521     // Collect phimail user id
522     $user = phimail_service_userID();
524     // Import the document
525     $return = addNewDocument($name, $mime_type, $fn, 0, filesize($fn), $user, 'direct');
527     // Remove the temporary file
528     @unlink($fn);
530     // Return the result
531     return $return;
535  * Send an error notification or other alert to the notification address specified in globals.
536  * (notification email function modified from interface/drugs/dispense_drug.php)
537  * @return true if notification successfully sent, false otherwise
538  */
539 function phimail_notify($subj, $body)
541     $recipient = $GLOBALS['practice_return_email_path'];
542     if (empty($recipient)) {
543         return false;
544     }
546     $mail = new PHPMailer();
547     $mail->From = $recipient;
548     $mail->FromName = 'phiMail Gateway';
549     $mail->isMail();
550     $mail->Host = "localhost";
551     $mail->Mailer = "mail";
552     $mail->Body = $body;
553     $mail->Subject = $subj;
554     $mail->AddAddress($recipient);
555     return ($mail->Send());