Fix for exporting a large number of lists.
[openemr.git] / library / direct_message_check.inc
blobe1304d1eb67d6a1201ae345701efedcf8f713743
1 <?php
3 /**
4  * Background receive function for phiMail Direct Messaging service.
5  *
6  * This script is called by the background service manager
7  * at /library/ajax/execute_background_services.php
8  *
9  * Copyright (C) 2013, 2021 EMR Direct <https://www.emrdirect.com/>
10  *
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>;.
21  *
22  * @package OpenEMR
23  * @author  EMR Direct <https://www.emrdirect.com/>
24  * @link    http://www.open-emr.org
25  */
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 OpenEMR\Common\Logging\SystemLogger;
34 use OpenEMR\Events\Core\Sanitize\IsAcceptedFileFilterEvent;
35 use OpenEMR\Services\VersionService;
36 use PHPMailer\PHPMailer\PHPMailer;
38 /**
39  * Connect to a phiMail Direct Messaging server
40  */
42 function phimail_connect(&$phimail_error)
45     if ($GLOBALS['phimail_enable'] == false) {
46         $phimail_error = 'C1';
47         return false; //for safety
48     }
50     $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
51     $phimail_username = $GLOBALS['phimail_username'];
52     $cryptoGen = new CryptoGen();
53     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
55     // if test mode is disabled we use the production cert, otherwise we use the test certificate.
56     if (isset($GLOBALS['phimail_testmode_disabled']) && $GLOBALS['phimail_testmode_disabled'] == '1') {
57         $phimail_cafile = $GLOBALS['fileroot'] . '/public/certs/phimail/phimail_server.pem';
58     } else {
59         $phimail_cafile = $GLOBALS['fileroot'] . '/public/certs/phimail/EMRDirectTestCA.pem';
60         (new SystemLogger())->debug("running phimail_connect in test mode.  This should not be used for production", ['ca' => $phimail_cafile, 'testmode' => $GLOBALS['phimail_testmode_disabled']]);
61     }
62     if (!file_exists($phimail_cafile)) {
63         $phimail_cafile = '';
64     }
66     $phimail_secure = true;
67     switch ($phimail_server['scheme']) {
68         case "tcp":
69         case "http":
70             $server = "tcp://" . $phimail_server['host'];
71             $phimail_secure = false;
72             break;
73         case "https":
74             $server = "ssl://" . $phimail_server['host']
75                 . ':' . $phimail_server['port'];
76             break;
77         case "ssl":
78         case "sslv3":
79         case "tls":
80             $server = $GLOBALS['phimail_server_address'];
81             break;
82         default:
83             $phimail_error = 'C2';
84             (new SystemLogger())->error("phimail_connect failed to connect due to invalid scheme", ['error' => $phimail_error]);
85             return false;
86     }
88     if ($phimail_secure) {
89         $context = stream_context_create();
90         if (
91             $phimail_cafile != '' &&
92             (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
93                 !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))
94         ) {
95             $phimail_error = 'C3';
96             (new SystemLogger())->error("phimail_connect failed to connect", ['error' => $phimail_error, 'server' => $server, 'ca' => $phimail_cafile]);
97             return false;
98         }
100         $socket_tries = 0;
101         $fp = false;
102         while ($socket_tries < 3 && !$fp) {
103             $socket_tries++;
104             $fp = @stream_socket_client(
105                 $server,
106                 $err1,
107                 $err2,
108                 10,
109                 STREAM_CLIENT_CONNECT,
110                 $context
111             );
112         }
114         if (!$fp) {
115             if ($err1 == '111') {
116                 $err2 = xl('Server may be offline');
117             }
119             if ($err2 == '') {
120                 $err2 = xl('Connection error');
121             }
123             $phimail_error = "C4 $err1 ($err2)";
124         } else {
125             (new SystemLogger())->debug("phimail_connect was successful");
126         }
127     } else {
128         $fp = @fsockopen($server, $phimail_server['port']);
129     }
131     if ($fp !== false) {
132         $ret = phimail_write_expect_OK($fp, "INFO VER OEMR " . (new VersionService())->asString() . " 1.3.2 "
133             . \PHP_VERSION . "\n");
134         if ($ret !== true) {
135             $fp = false;
136             $phimail_error = 'C5';
137         }
138     }
140     if (!empty($phimail_error)) {
141         (new SystemLogger())->error("phimail_connect failed to connect", ['error' => $phimail_error, 'server' => $server, 'ca' => $phimail_cafile]);
142     } elseif ($fp !== false) {
143         (new SystemLogger())->debug("phimail_connect was successful");
144     } else {
145         (new SystemLogger())->error("phimail_connect failed to connect with unknown error", ['error' => $phimail_error, 'server' => $server, 'port' => $phimail_server['port']]);
146     }
148     return $fp;
152  * Connect to a phiMail Direct Messaging server and check for any incoming status
153  * messages related to previously transmitted messages or any new messages received.
154  */
156 function phimail_check()
158     $fp = phimail_connect($err);
159     if ($fp === false) {
160         phimail_logit(0, xl('could not connect to server') . ' ' . $err);
161         return;
162     }
164     $phimail_username = $GLOBALS['phimail_username'];
165     $cryptoGen = new CryptoGen();
166     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
168     $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
169     if ($ret !== true) {
170         phimail_logit(0, "authentication error " . $ret);
171         return;
172     }
174     if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
175         $notifyUsername = 'admin'; //fallback
176     }
178     while (1) {
179         phimail_write($fp, "CHECK\n");
180         $ret = fgets($fp, 512);
182         if ($ret == "NONE\n") { //nothing to process
183             phimail_close($fp);
184             phimail_logit(1, "message check completed");
185             return;
186         } elseif (substr($ret, 0, 6) == "STATUS") {
187             //Format STATUS message-id status-code [additional-information]
188             $val = explode(" ", trim($ret), 4);
189             $sql = 'SELECT * from direct_message_log WHERE msg_id = ?';
190             $res = sqlStatementNoLog($sql, array($val[1]));
191             if ($res === false) { //database problem
192                 phimail_close($fp);
193                 phimail_logit(0, "database problem");
194                 return;
195             }
197             if (($msg = sqlFetchArray($res)) === false) {
198                 //no match, so log it and move on (should never happen)
199                 phimail_logit(0, "NO MATCH: " . $ret);
200                 $ret = phimail_write_expect_OK($fp, "OK\n");
201                 if ($ret !== true) {
202                     phimail_logit(0, "M1 status acknowledgment failed: " . $ret);
203                     return;
204                 } else {
205                     continue;
206                 }
207             }
209             //if we get here, $msg contains the matching outgoing message record
210             if ($val[2] == 'failed') {
211                 $success = 0;
212                 $status = 'F';
213             } elseif ($val[2] == 'dispatched') {
214                 $success = 1;
215                 $status = 'D';
216             } else {
217                 //unrecognized status, log it and move on (should never happen)
218                 $ret = "UNKNOWN STATUS: " . $ret;
219                 $success = 0;
220                 $status = 'U';
221             }
223             phimail_logit($success, $ret, $msg['patient_id']);
225             if (!isset($val[3])) {
226                 $val[3] = "";
227             }
229             $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
230             $res = sqlStatementNoLog($sql, array($status, $val[3], $val[1]));
231             if ($res === false) { //database problem
232                 phimail_close($fp);
233                 phimail_logit(0, "database problem updating: " . $val[1]);
234                 return;
235             }
237             if (!$success) {
238                 //notify local user of failure
239                 $sql = "SELECT username FROM users WHERE id = ?";
240                 $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
241                 $fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
242                     xl('unknown (see log)') : $user_row['username'];
243                 $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
244                     . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
245                 phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
246                 $pnote_id = addPnote(
247                     $msg['patient_id'],
248                     xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
249                     0,
250                     1,
251                     "Unassigned",
252                     $notifyUsername,
253                     "",
254                     "New",
255                     "phimail-service"
256                 );
257             }
259             //done with this status message
260             $ret = phimail_write_expect_OK($fp, "OK\n");
261             if ($ret !== true) {
262                 phimail_logit(0, "M2 status acknowledgment failed: " . $ret);
263                 phimail_close($fp);
264                 return;
265             }
266         } elseif (substr($ret, 0, 4) == "MAIL") {
267             $val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
268             $recipient = $val[1];
269             $sender = $val[2];
270             $att = (int)$val[3];
271             $msg_id = $val[4];
273             //request main message
274             $ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
275             if ($ret2 !== true) {
276                 phimail_logit(0, "M3 SHOW 0 failed: " . $ret2);
277                 phimail_close($fp);
278                 return;
279             }
281             //get message headers
282             $hdrs = "";
283             while (($next_hdr = fgets($fp, 1024)) != "\n") {
284                 $hdrs .= $next_hdr;
285             }
287             $mime_type = fgets($fp, 512);
288             $mime_info = explode(";", $mime_type);
289             $mime_type_main = trim(strtolower($mime_info[0]));
291             //get main message body
292             $body_len = fgets($fp, 256);
293             $body = phimail_read_blob($fp, $body_len);
294             if ($body === false) {
295                 phimail_logit(0, "M4 read body failed");
296                 phimail_close($fp);
297                 return;
298             }
300             $att2 = fgets($fp, 256);
301             if ($att2 != $att) { //safety for mismatch on attachments
302                 phimail_logit(0, "M5 attachment mismatch");
303                 phimail_close($fp);
304                 return;
305             }
307             //get attachment info
308             if ($att > 0) {
309                 for ($attnum = 0; $attnum < $att; $attnum++) {
310                     if (
311                         ($attinfo[$attnum]['name'] = fgets($fp, 1024)) === false
312                         || ($attinfo[$attnum]['mime'] = fgets($fp, 1024)) === false
313                         || ($attinfo[$attnum]['desc'] = fgets($fp, 1024)) === false
314                     ) {
315                         phimail_logit(0, "M6 read attachment " . ($attnum + 1) . " metadata failed");
316                         phimail_close($fp);
317                         return;
318                     }
319                 }
320             }
322             //main part gets stored as document if not plain text content
323             //(if plain text it will be the body of the final pnote)
324             $all_doc_ids = array();
325             $doc_id = 0;
326             $att_detail = "";
327             if ($mime_type_main != "text/plain" && $mime_type_main != "text/html") {
328                 if ($body_len == 0) {
329                     $att_detail = $att_detail . "\n" . xl("Zero length attachment") . " ($mime_type_main; " .
330                         "0 bytes - " . xl("empty file received") . ") Main message body";
331                     unlink($body);
332                 } else {
333                     $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
334                     $doc_id = phimail_store($name, $mime_type_main, $body);
335                     if (!$doc_id) {
336                         phimail_logit(0, "M7 store non-text body failed");
337                         phimail_close($fp);
338                         return;
339                     }
341                     $idnum = $doc_id['doc_id'];
342                     $all_doc_ids[] = $idnum;
343                     $url = $doc_id['url'];
344                     $url = substr($url, strrpos($url, "/") + 1);
345                     $att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
346                         filesize($body) . " bytes) Main message body";
347                 }
348             }
350             //download and store attachments
351             for ($attnum = 0; $attnum < $att; $attnum++) {
352                 $ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum + 1) . "\n");
353                 if ($ret2 !== true) {
354                     phimail_logit(0, "M8 SHOW " . ($attnum + 1) . " failed: " . $ret2);
355                     phimail_close($fp);
356                     return;
357                 }
359                 //we can ignore next two lines (repeat of name and mime-type)
360                 if (($a1 = fgets($fp, 512)) === false || ($a2 = fgets($fp, 512)) === false) {
361                     phimail_logit(0, "M9 skip attachment " . ($attnum + 1) . " duplicate header lines failed");
362                     phimail_close($fp);
363                     return;
364                 }
366                 $att_len = fgets($fp, 256); //length of file
367                 $attdata = phimail_read_blob($fp, $att_len);
368                 if ($attdata === false) {
369                     phimail_logit(0, "M10 read attachment " . ($attnum + 1) . " failed");
370                     phimail_close($fp);
371                     return;
372                 }
374                 $attinfo[$attnum]['file'] = $attdata;
376                 $req_name = trim($attinfo[$attnum]['name']);
377                 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
378                 $attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
379                 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
381                 if ($att_len == 0) {
382                     $att_detail = $att_detail . "\n" . xl("Zero length attachment") . " ($attmime; " .
383                         "0 bytes - " . xl("empty file received") . " " . trim($attinfo[$attnum]['desc']);
384                     unlink($attdata);
385                 } else {
386                     $att_doc_id = phimail_store($req_name, $attmime, $attdata);
387                     if (!$att_doc_id) {
388                         phimail_logit(0, "M11 store attachment " . ($attnum + 1) . " failed");
389                         phimail_close($fp);
390                         return;
391                     }
393                     $attinfo[$attnum]['doc_id'] = $att_doc_id;
394                     $idnum = $att_doc_id['doc_id'];
395                     $all_doc_ids[] = $idnum;
396                     $url = $att_doc_id['url'];
397                     $url = substr($url, strrpos($url, "/") + 1);
398                     $att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
399                         $att_doc_id['filesize'] . " bytes) " . trim($attinfo[$attnum]['desc']);
400                 }
401             }
403             if ($att_detail != "") {
404                 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
405             }
407             $ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
409             //logging only after succesful download, storage, and acknowledgement of message
410             $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
411                 "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
412             $res = sqlStatementNoLog($sql, array($msg_id, $sender, $recipient, phimail_service_userID()));
414             phimail_logit(1, $ret);
416             //alert appointed user about new message
417             switch ($mime_type_main) {
418                 case "text/plain":
419                     $body_text = @file_get_contents($body); //this was not uploaded as a document
420                     unlink($body);
421                     if (empty($body_text ?? '')) {
422                         $body_text = xl("Please note, this message was received empty and is not an error.");
423                     }
424                     $pnote_id = addPnote(
425                         0,
426                         xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
427                         0,
428                         1,
429                         "Unassigned",
430                         $notifyUsername,
431                         "",
432                         "New",
433                         "phimail-service"
434                     );
435                     break;
436                 case "text/html":
437                     $body_text = @file_get_contents($body);
438                     unlink($body);
439                     if (empty($body_text ?? '')) {
440                         $body_text = xl("Please note, this message was received empty and is not an error.");
441                     } else {
442                         // meager attempt to covert to text. @TODO convert our Messages message body from textarea to div so can display html.
443                         $body_text = trim(html_entity_decode(strip_tags(str_ireplace(["<br />", "<br>", "<br/>"], PHP_EOL, $body_text))));
444                     }
445                     $pnote_id = addPnote(
446                         0,
447                         xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
448                         0,
449                         1,
450                         "Unassigned",
451                         $notifyUsername,
452                         "",
453                         "New",
454                         "phimail-service"
455                     );
456                     break;
458                 default:
459                     $note = xl("Direct Message Received.") . "\n$hdrs\n"
460                         . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
461                     $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
462                     break;
463             }
465             foreach ($all_doc_ids as $doc_id) {
466                 setGpRelation(1, $doc_id, 6, $pnote_id);
467             }
469             if ($ret2 !== true) {
470                 phimail_logit(0, "M12 DONE failed: " . $ret2);
471                 phimail_close();
472                 return;
473             }
474         } else { //unrecognized or FAIL response
475             phimail_logit(0, "M16 unexpected problem checking messages " . $ret);
476             phimail_close($fp);
477             return;
478         }
479     }
483  * Helper functions
484  */
485 function phimail_write($fp, $text)
487     fwrite($fp, $text);
488     fflush($fp);
491 function phimail_write_expect_OK($fp, $text)
493     phimail_write($fp, $text);
494     $ret = fgets($fp, 256);
495     if ($ret != "OK\n") { //unexpected error
496         phimail_close($fp);
497         return $ret;
498     }
500     return true;
503 function phimail_close($fp)
505     fclose($fp);
508 function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
510     if (!$success) {
511         (new SystemLogger())->errorLogCaller($event, ['success' => $success, 'text' => $text, 'pid' => $pid]);
512     }
513     EventAuditLogger::instance()->newEvent($event, "phimail-service", 0, $success, $text, $pid);
517  * Read a blob of data into a local temporary file
519  * @param $len number of bytes to read
520  * @return the temp filename, or FALSE if failure
521  */
522 function phimail_read_blob($fp, $len)
525     $fpath = $GLOBALS['temporary_files_dir'];
526     if (!@file_exists($fpath)) {
527         phimail_logit(0, "M13 temp dir does not exist: " . $fpath);
528         return false;
529     }
531     $name = uniqid("direct-");
532     $fn = $fpath . "/" . $name . ".dat";
533     $dup = 1;
534     while (file_exists($fn)) {
535         $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
536     }
538     $ff = @fopen($fn, "w");
539     if (!$ff) {
540         phimail_logit(0, "M14 failed opening temp file: " . $fn);
541         return false;
542     }
544     $bytes_left = $len;
545     $chunk_size = 1024;
546     while (!feof($fp) && $bytes_left > 0) {
547         if ($bytes_left < $chunk_size) {
548             $chunk_size = $bytes_left;
549         }
551         $chunk = fread($fp, $chunk_size);
552         if ($chunk === false || @fwrite($ff, $chunk) === false) {
553             @fclose($ff);
554             @unlink($fn);
555             phimail_logit(0, "M15 failure " . ($chunk === false ? "reading" : "writing")
556                 . " blob chunk after " . ($len - $bytes_left) . " bytes");
557             return false;
558         }
560         $bytes_left -= strlen($chunk);
561     }
563     @fclose($ff);
564     return ($fn);
568  * Return a suitable filename extension based on MIME-type
569  * (very limited, default is .dat)
570  */
571 function phimail_extension($mime)
573     $m = explode("/", $mime);
574     switch ($mime) {
575         case 'text/plain':
576             return (".txt");
577         default:
578     }
580     switch ($m[1]) {
581         case 'html':
582         case 'xml':
583         case 'pdf':
584             return ("." . $m[1]);
585         default:
586             return (".dat");
587     }
590 function phimail_service_userID($name = 'phimail-service')
592     $sql = "SELECT id FROM users WHERE username=?";
593     if (
594         ($r = sqlStatementNoLog($sql, array($name))) === false ||
595         ($u = sqlFetchArray($r)) === false
596     ) {
597         $user = 1; //default if we don't have a service user
598     } else {
599         $user = $u['id'];
600     }
602     return ($user);
606  * Given an IsAcceptedFileFilterEvent from our isWhitelist function we check to make sure that we allow
607  * the mime type of the Direct message attachment that was sent by the server.
609  * @param IsAcceptedFileFilterEvent $event The event with the mimetype to check if we allow it or not
610  * @return IsAcceptedFileFilterEvent
611  */
612 function phimail_allow_document_mimetype(IsAcceptedFileFilterEvent $event)
614     global $phimail_direct_message_check_allowed_mimetype;
615     $isAllowedFile = $event->isAllowedFile();
616     if (!$isAllowedFile) {
617         // we used to only bypass if the Direct mime type matched with what comes through in the event.
618         // This fails though if there are multiple possible mime types such as application/xml vs text/xml and the Direct
619         // mime type differs from the local OS detection. We will just bypass the mime check alltogether.
620         $event->setAllowedFile(true);
621     }
622     return $event;
627  * Registers an attachment or non-text message file using the existing Document structure
629  * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
630  */
631 function phimail_store($name, $mime_type, $fn)
633     global $phimail_direct_message_check_allowed_mimetype;
635     $allowMimeTypeFunction = 'phimail_allow_document_mimetype';
636     // we bypass the whitelisting JUST for phimail documents
637     if (isset($GLOBALS['kernel'])) {
638         $GLOBALS['kernel']->getEventDispatcher()
639             ->addListener(IsAcceptedFileFilterEvent::EVENT_FILTER_IS_ACCEPTED_FILE, $allowMimeTypeFunction);
640     }
641     // Collect phimail user id
642     $user = phimail_service_userID();
643     try {
644         // Import the document
645         $phimail_direct_message_check_allowed_mimetype = $mime_type;
646         $filesize = filesize($fn);
647         $return = addNewDocument($name, $mime_type, $fn, 0, $filesize, $user, 'direct');
648         if (is_array($return)) {
649             $return['filesize'] = $filesize;
650         }
651     } catch (\Exception $exception) {
652         (new SystemLogger())->errorLogCaller($exception->getMessage(), ['name' => $name, 'mime_type' => $mime_type, 'fn' => $fn]);
653         phimail_logit(0, "problem storing attachment in OpenEMR");
654         $return = false;
655     } finally {
656         $phimail_direct_message_check_allowed_mimetype = null;
657         // There shouldn't be another request in the system to add a document, but for security sake we will prevent code
658         // after this from bypassing the whitelist filter
659         if (isset($GLOBALS['kernel'])) {
660             $GLOBALS['kernel']->getEventDispatcher()
661                 ->removeListener(IsAcceptedFileFilterEvent::EVENT_FILTER_IS_ACCEPTED_FILE, $allowMimeTypeFunction);
662         }
663         // Remove the temporary file
664         @unlink($fn);
665     }
667     // Return the result
668     return $return;
672  * Send an error notification or other alert to the notification address specified in globals.
673  * (notification email function modified from interface/drugs/dispense_drug.php)
675  * @return true if notification successfully sent, false otherwise
676  */
677 function phimail_notify($subj, $body)
679     $recipient = $GLOBALS['practice_return_email_path'];
680     if (empty($recipient)) {
681         return false;
682     }
684     $mail = new PHPMailer();
685     $mail->From = $recipient;
686     $mail->FromName = 'phiMail Gateway';
687     $mail->isMail();
688     $mail->Host = "localhost";
689     $mail->Mailer = "mail";
690     $mail->Body = $body;
691     $mail->Subject = $subj;
692     $mail->AddAddress($recipient);
693     return ($mail->Send());