You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
695 lines
25 KiB
695 lines
25 KiB
2 years ago
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Background receive function for phiMail Direct Messaging service.
|
||
|
*
|
||
|
* This script is called by the background service manager
|
||
|
* at /library/ajax/execute_background_services.php
|
||
|
*
|
||
|
* Copyright (C) 2013, 2021 EMR Direct <https://www.emrdirect.com/>
|
||
|
*
|
||
|
* LICENSE: This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; either version 2
|
||
|
* of the License, or (at your option) any later version.
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
|
||
|
*
|
||
|
* @package OpenEMR
|
||
|
* @author EMR Direct <https://www.emrdirect.com/>
|
||
|
* @link http://www.open-emr.org
|
||
|
*/
|
||
|
|
||
|
require_once(dirname(__FILE__) . "/pnotes.inc");
|
||
|
require_once(dirname(__FILE__) . "/documents.php");
|
||
|
require_once(dirname(__FILE__) . "/gprelations.inc.php");
|
||
|
|
||
|
use OpenEMR\Common\Crypto\CryptoGen;
|
||
|
use OpenEMR\Common\Logging\EventAuditLogger;
|
||
|
use OpenEMR\Common\Logging\SystemLogger;
|
||
|
use OpenEMR\Events\Core\Sanitize\IsAcceptedFileFilterEvent;
|
||
|
use OpenEMR\Services\VersionService;
|
||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||
|
|
||
|
/**
|
||
|
* Connect to a phiMail Direct Messaging server
|
||
|
*/
|
||
|
|
||
|
function phimail_connect(&$phimail_error)
|
||
|
{
|
||
|
|
||
|
if ($GLOBALS['phimail_enable'] == false) {
|
||
|
$phimail_error = 'C1';
|
||
|
return false; //for safety
|
||
|
}
|
||
|
|
||
|
$phimail_server = @parse_url($GLOBALS['phimail_server_address']);
|
||
|
$phimail_username = $GLOBALS['phimail_username'];
|
||
|
$cryptoGen = new CryptoGen();
|
||
|
$phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
|
||
|
|
||
|
// if test mode is disabled we use the production cert, otherwise we use the test certificate.
|
||
|
if (isset($GLOBALS['phimail_testmode_disabled']) && $GLOBALS['phimail_testmode_disabled'] == '1') {
|
||
|
$phimail_cafile = $GLOBALS['fileroot'] . '/public/certs/phimail/phimail_server.pem';
|
||
|
} else {
|
||
|
$phimail_cafile = $GLOBALS['fileroot'] . '/public/certs/phimail/EMRDirectTestCA.pem';
|
||
|
(new SystemLogger())->debug("running phimail_connect in test mode. This should not be used for production", ['ca' => $phimail_cafile, 'testmode' => $GLOBALS['phimail_testmode_disabled']]);
|
||
|
}
|
||
|
if (!file_exists($phimail_cafile)) {
|
||
|
$phimail_cafile = '';
|
||
|
}
|
||
|
|
||
|
$phimail_secure = true;
|
||
|
switch ($phimail_server['scheme']) {
|
||
|
case "tcp":
|
||
|
case "http":
|
||
|
$server = "tcp://" . $phimail_server['host'];
|
||
|
$phimail_secure = false;
|
||
|
break;
|
||
|
case "https":
|
||
|
$server = "ssl://" . $phimail_server['host']
|
||
|
. ':' . $phimail_server['port'];
|
||
|
break;
|
||
|
case "ssl":
|
||
|
case "sslv3":
|
||
|
case "tls":
|
||
|
$server = $GLOBALS['phimail_server_address'];
|
||
|
break;
|
||
|
default:
|
||
|
$phimail_error = 'C2';
|
||
|
(new SystemLogger())->error("phimail_connect failed to connect due to invalid scheme", ['error' => $phimail_error]);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($phimail_secure) {
|
||
|
$context = stream_context_create();
|
||
|
if (
|
||
|
$phimail_cafile != '' &&
|
||
|
(!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
|
||
|
!stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))
|
||
|
) {
|
||
|
$phimail_error = 'C3';
|
||
|
(new SystemLogger())->error("phimail_connect failed to connect", ['error' => $phimail_error, 'server' => $server, 'ca' => $phimail_cafile]);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$socket_tries = 0;
|
||
|
$fp = false;
|
||
|
while ($socket_tries < 3 && !$fp) {
|
||
|
$socket_tries++;
|
||
|
$fp = @stream_socket_client(
|
||
|
$server,
|
||
|
$err1,
|
||
|
$err2,
|
||
|
10,
|
||
|
STREAM_CLIENT_CONNECT,
|
||
|
$context
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (!$fp) {
|
||
|
if ($err1 == '111') {
|
||
|
$err2 = xl('Server may be offline');
|
||
|
}
|
||
|
|
||
|
if ($err2 == '') {
|
||
|
$err2 = xl('Connection error');
|
||
|
}
|
||
|
|
||
|
$phimail_error = "C4 $err1 ($err2)";
|
||
|
} else {
|
||
|
(new SystemLogger())->debug("phimail_connect was successful");
|
||
|
}
|
||
|
} else {
|
||
|
$fp = @fsockopen($server, $phimail_server['port']);
|
||
|
}
|
||
|
|
||
|
if ($fp !== false) {
|
||
|
$ret = phimail_write_expect_OK($fp, "INFO VER OEMR " . (new VersionService())->asString() . " 1.3.2 "
|
||
|
. \PHP_VERSION . "\n");
|
||
|
if ($ret !== true) {
|
||
|
$fp = false;
|
||
|
$phimail_error = 'C5';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!empty($phimail_error)) {
|
||
|
(new SystemLogger())->error("phimail_connect failed to connect", ['error' => $phimail_error, 'server' => $server, 'ca' => $phimail_cafile]);
|
||
|
} elseif ($fp !== false) {
|
||
|
(new SystemLogger())->debug("phimail_connect was successful");
|
||
|
} else {
|
||
|
(new SystemLogger())->error("phimail_connect failed to connect with unknown error", ['error' => $phimail_error, 'server' => $server, 'port' => $phimail_server['port']]);
|
||
|
}
|
||
|
|
||
|
return $fp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Connect to a phiMail Direct Messaging server and check for any incoming status
|
||
|
* messages related to previously transmitted messages or any new messages received.
|
||
|
*/
|
||
|
|
||
|
function phimail_check()
|
||
|
{
|
||
|
$fp = phimail_connect($err);
|
||
|
if ($fp === false) {
|
||
|
phimail_logit(0, xl('could not connect to server') . ' ' . $err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$phimail_username = $GLOBALS['phimail_username'];
|
||
|
$cryptoGen = new CryptoGen();
|
||
|
$phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
|
||
|
|
||
|
$ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
|
||
|
if ($ret !== true) {
|
||
|
phimail_logit(0, "authentication error " . $ret);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
|
||
|
$notifyUsername = 'admin'; //fallback
|
||
|
}
|
||
|
|
||
|
while (1) {
|
||
|
phimail_write($fp, "CHECK\n");
|
||
|
$ret = fgets($fp, 512);
|
||
|
|
||
|
if ($ret == "NONE\n") { //nothing to process
|
||
|
phimail_close($fp);
|
||
|
phimail_logit(1, "message check completed");
|
||
|
return;
|
||
|
} elseif (substr($ret, 0, 6) == "STATUS") {
|
||
|
//Format STATUS message-id status-code [additional-information]
|
||
|
$val = explode(" ", trim($ret), 4);
|
||
|
$sql = 'SELECT * from direct_message_log WHERE msg_id = ?';
|
||
|
$res = sqlStatementNoLog($sql, array($val[1]));
|
||
|
if ($res === false) { //database problem
|
||
|
phimail_close($fp);
|
||
|
phimail_logit(0, "database problem");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (($msg = sqlFetchArray($res)) === false) {
|
||
|
//no match, so log it and move on (should never happen)
|
||
|
phimail_logit(0, "NO MATCH: " . $ret);
|
||
|
$ret = phimail_write_expect_OK($fp, "OK\n");
|
||
|
if ($ret !== true) {
|
||
|
phimail_logit(0, "M1 status acknowledgment failed: " . $ret);
|
||
|
return;
|
||
|
} else {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//if we get here, $msg contains the matching outgoing message record
|
||
|
if ($val[2] == 'failed') {
|
||
|
$success = 0;
|
||
|
$status = 'F';
|
||
|
} elseif ($val[2] == 'dispatched') {
|
||
|
$success = 1;
|
||
|
$status = 'D';
|
||
|
} else {
|
||
|
//unrecognized status, log it and move on (should never happen)
|
||
|
$ret = "UNKNOWN STATUS: " . $ret;
|
||
|
$success = 0;
|
||
|
$status = 'U';
|
||
|
}
|
||
|
|
||
|
phimail_logit($success, $ret, $msg['patient_id']);
|
||
|
|
||
|
if (!isset($val[3])) {
|
||
|
$val[3] = "";
|
||
|
}
|
||
|
|
||
|
$sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
|
||
|
$res = sqlStatementNoLog($sql, array($status, $val[3], $val[1]));
|
||
|
if ($res === false) { //database problem
|
||
|
phimail_close($fp);
|
||
|
phimail_logit(0, "database problem updating: " . $val[1]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!$success) {
|
||
|
//notify local user of failure
|
||
|
$sql = "SELECT username FROM users WHERE id = ?";
|
||
|
$res2 = sqlStatementNoLog($sql, array($msg['user_id']));
|
||
|
$fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
|
||
|
xl('unknown (see log)') : $user_row['username'];
|
||
|
$fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
|
||
|
. "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
|
||
|
phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
|
||
|
$pnote_id = addPnote(
|
||
|
$msg['patient_id'],
|
||
|
xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
|
||
|
0,
|
||
|
1,
|
||
|
"Unassigned",
|
||
|
$notifyUsername,
|
||
|
"",
|
||
|
"New",
|
||
|
"phimail-service"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//done with this status message
|
||
|
$ret = phimail_write_expect_OK($fp, "OK\n");
|
||
|
if ($ret !== true) {
|
||
|
phimail_logit(0, "M2 status acknowledgment failed: " . $ret);
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
} elseif (substr($ret, 0, 4) == "MAIL") {
|
||
|
$val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
|
||
|
$recipient = $val[1];
|
||
|
$sender = $val[2];
|
||
|
$att = (int)$val[3];
|
||
|
$msg_id = $val[4];
|
||
|
|
||
|
//request main message
|
||
|
$ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
|
||
|
if ($ret2 !== true) {
|
||
|
phimail_logit(0, "M3 SHOW 0 failed: " . $ret2);
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//get message headers
|
||
|
$hdrs = "";
|
||
|
while (($next_hdr = fgets($fp, 1024)) != "\n") {
|
||
|
$hdrs .= $next_hdr;
|
||
|
}
|
||
|
|
||
|
$mime_type = fgets($fp, 512);
|
||
|
$mime_info = explode(";", $mime_type);
|
||
|
$mime_type_main = trim(strtolower($mime_info[0]));
|
||
|
|
||
|
//get main message body
|
||
|
$body_len = fgets($fp, 256);
|
||
|
$body = phimail_read_blob($fp, $body_len);
|
||
|
if ($body === false) {
|
||
|
phimail_logit(0, "M4 read body failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$att2 = fgets($fp, 256);
|
||
|
if ($att2 != $att) { //safety for mismatch on attachments
|
||
|
phimail_logit(0, "M5 attachment mismatch");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//get attachment info
|
||
|
if ($att > 0) {
|
||
|
for ($attnum = 0; $attnum < $att; $attnum++) {
|
||
|
if (
|
||
|
($attinfo[$attnum]['name'] = fgets($fp, 1024)) === false
|
||
|
|| ($attinfo[$attnum]['mime'] = fgets($fp, 1024)) === false
|
||
|
|| ($attinfo[$attnum]['desc'] = fgets($fp, 1024)) === false
|
||
|
) {
|
||
|
phimail_logit(0, "M6 read attachment " . ($attnum + 1) . " metadata failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//main part gets stored as document if not plain text content
|
||
|
//(if plain text it will be the body of the final pnote)
|
||
|
$all_doc_ids = array();
|
||
|
$doc_id = 0;
|
||
|
$att_detail = "";
|
||
|
if ($mime_type_main != "text/plain" && $mime_type_main != "text/html") {
|
||
|
if ($body_len == 0) {
|
||
|
$att_detail = $att_detail . "\n" . xl("Zero length attachment") . " ($mime_type_main; " .
|
||
|
"0 bytes - " . xl("empty file received") . ") Main message body";
|
||
|
unlink($body);
|
||
|
} else {
|
||
|
$name = uniqid("dm-message-") . phimail_extension($mime_type_main);
|
||
|
$doc_id = phimail_store($name, $mime_type_main, $body);
|
||
|
if (!$doc_id) {
|
||
|
phimail_logit(0, "M7 store non-text body failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$idnum = $doc_id['doc_id'];
|
||
|
$all_doc_ids[] = $idnum;
|
||
|
$url = $doc_id['url'];
|
||
|
$url = substr($url, strrpos($url, "/") + 1);
|
||
|
$att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
|
||
|
filesize($body) . " bytes) Main message body";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//download and store attachments
|
||
|
for ($attnum = 0; $attnum < $att; $attnum++) {
|
||
|
$ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum + 1) . "\n");
|
||
|
if ($ret2 !== true) {
|
||
|
phimail_logit(0, "M8 SHOW " . ($attnum + 1) . " failed: " . $ret2);
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//we can ignore next two lines (repeat of name and mime-type)
|
||
|
if (($a1 = fgets($fp, 512)) === false || ($a2 = fgets($fp, 512)) === false) {
|
||
|
phimail_logit(0, "M9 skip attachment " . ($attnum + 1) . " duplicate header lines failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$att_len = fgets($fp, 256); //length of file
|
||
|
$attdata = phimail_read_blob($fp, $att_len);
|
||
|
if ($attdata === false) {
|
||
|
phimail_logit(0, "M10 read attachment " . ($attnum + 1) . " failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$attinfo[$attnum]['file'] = $attdata;
|
||
|
|
||
|
$req_name = trim($attinfo[$attnum]['name']);
|
||
|
$req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
|
||
|
$attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
|
||
|
$attmime = strtolower($attinfo[$attnum]['mime'][0]);
|
||
|
|
||
|
if ($att_len == 0) {
|
||
|
$att_detail = $att_detail . "\n" . xl("Zero length attachment") . " ($attmime; " .
|
||
|
"0 bytes - " . xl("empty file received") . " " . trim($attinfo[$attnum]['desc']);
|
||
|
unlink($attdata);
|
||
|
} else {
|
||
|
$att_doc_id = phimail_store($req_name, $attmime, $attdata);
|
||
|
if (!$att_doc_id) {
|
||
|
phimail_logit(0, "M11 store attachment " . ($attnum + 1) . " failed");
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$attinfo[$attnum]['doc_id'] = $att_doc_id;
|
||
|
$idnum = $att_doc_id['doc_id'];
|
||
|
$all_doc_ids[] = $idnum;
|
||
|
$url = $att_doc_id['url'];
|
||
|
$url = substr($url, strrpos($url, "/") + 1);
|
||
|
$att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
|
||
|
$att_doc_id['filesize'] . " bytes) " . trim($attinfo[$attnum]['desc']);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($att_detail != "") {
|
||
|
$att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
|
||
|
}
|
||
|
|
||
|
$ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
|
||
|
|
||
|
//logging only after succesful download, storage, and acknowledgement of message
|
||
|
$sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
|
||
|
"VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
|
||
|
$res = sqlStatementNoLog($sql, array($msg_id, $sender, $recipient, phimail_service_userID()));
|
||
|
|
||
|
phimail_logit(1, $ret);
|
||
|
|
||
|
//alert appointed user about new message
|
||
|
switch ($mime_type_main) {
|
||
|
case "text/plain":
|
||
|
$body_text = @file_get_contents($body); //this was not uploaded as a document
|
||
|
unlink($body);
|
||
|
if (empty($body_text ?? '')) {
|
||
|
$body_text = xl("Please note, this message was received empty and is not an error.");
|
||
|
}
|
||
|
$pnote_id = addPnote(
|
||
|
0,
|
||
|
xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
|
||
|
0,
|
||
|
1,
|
||
|
"Unassigned",
|
||
|
$notifyUsername,
|
||
|
"",
|
||
|
"New",
|
||
|
"phimail-service"
|
||
|
);
|
||
|
break;
|
||
|
case "text/html":
|
||
|
$body_text = @file_get_contents($body);
|
||
|
unlink($body);
|
||
|
if (empty($body_text ?? '')) {
|
||
|
$body_text = xl("Please note, this message was received empty and is not an error.");
|
||
|
} else {
|
||
|
// meager attempt to covert to text. @TODO convert our Messages message body from textarea to div so can display html.
|
||
|
$body_text = trim(html_entity_decode(strip_tags(str_ireplace(["<br />", "<br>", "<br/>"], PHP_EOL, $body_text))));
|
||
|
}
|
||
|
$pnote_id = addPnote(
|
||
|
0,
|
||
|
xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
|
||
|
0,
|
||
|
1,
|
||
|
"Unassigned",
|
||
|
$notifyUsername,
|
||
|
"",
|
||
|
"New",
|
||
|
"phimail-service"
|
||
|
);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
$note = xl("Direct Message Received.") . "\n$hdrs\n"
|
||
|
. xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
|
||
|
$pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
foreach ($all_doc_ids as $doc_id) {
|
||
|
setGpRelation(1, $doc_id, 6, $pnote_id);
|
||
|
}
|
||
|
|
||
|
if ($ret2 !== true) {
|
||
|
phimail_logit(0, "M12 DONE failed: " . $ret2);
|
||
|
phimail_close();
|
||
|
return;
|
||
|
}
|
||
|
} else { //unrecognized or FAIL response
|
||
|
phimail_logit(0, "M16 unexpected problem checking messages " . $ret);
|
||
|
phimail_close($fp);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper functions
|
||
|
*/
|
||
|
function phimail_write($fp, $text)
|
||
|
{
|
||
|
fwrite($fp, $text);
|
||
|
fflush($fp);
|
||
|
}
|
||
|
|
||
|
function phimail_write_expect_OK($fp, $text)
|
||
|
{
|
||
|
phimail_write($fp, $text);
|
||
|
$ret = fgets($fp, 256);
|
||
|
if ($ret != "OK\n") { //unexpected error
|
||
|
phimail_close($fp);
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function phimail_close($fp)
|
||
|
{
|
||
|
fclose($fp);
|
||
|
}
|
||
|
|
||
|
function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
|
||
|
{
|
||
|
if (!$success) {
|
||
|
(new SystemLogger())->errorLogCaller($event, ['success' => $success, 'text' => $text, 'pid' => $pid]);
|
||
|
}
|
||
|
EventAuditLogger::instance()->newEvent($event, "phimail-service", 0, $success, $text, $pid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read a blob of data into a local temporary file
|
||
|
*
|
||
|
* @param $len number of bytes to read
|
||
|
* @return the temp filename, or FALSE if failure
|
||
|
*/
|
||
|
function phimail_read_blob($fp, $len)
|
||
|
{
|
||
|
|
||
|
$fpath = $GLOBALS['temporary_files_dir'];
|
||
|
if (!@file_exists($fpath)) {
|
||
|
phimail_logit(0, "M13 temp dir does not exist: " . $fpath);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$name = uniqid("direct-");
|
||
|
$fn = $fpath . "/" . $name . ".dat";
|
||
|
$dup = 1;
|
||
|
while (file_exists($fn)) {
|
||
|
$fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
|
||
|
}
|
||
|
|
||
|
$ff = @fopen($fn, "w");
|
||
|
if (!$ff) {
|
||
|
phimail_logit(0, "M14 failed opening temp file: " . $fn);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$bytes_left = $len;
|
||
|
$chunk_size = 1024;
|
||
|
while (!feof($fp) && $bytes_left > 0) {
|
||
|
if ($bytes_left < $chunk_size) {
|
||
|
$chunk_size = $bytes_left;
|
||
|
}
|
||
|
|
||
|
$chunk = fread($fp, $chunk_size);
|
||
|
if ($chunk === false || @fwrite($ff, $chunk) === false) {
|
||
|
@fclose($ff);
|
||
|
@unlink($fn);
|
||
|
phimail_logit(0, "M15 failure " . ($chunk === false ? "reading" : "writing")
|
||
|
. " blob chunk after " . ($len - $bytes_left) . " bytes");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$bytes_left -= strlen($chunk);
|
||
|
}
|
||
|
|
||
|
@fclose($ff);
|
||
|
return ($fn);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a suitable filename extension based on MIME-type
|
||
|
* (very limited, default is .dat)
|
||
|
*/
|
||
|
function phimail_extension($mime)
|
||
|
{
|
||
|
$m = explode("/", $mime);
|
||
|
switch ($mime) {
|
||
|
case 'text/plain':
|
||
|
return (".txt");
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
switch ($m[1]) {
|
||
|
case 'html':
|
||
|
case 'xml':
|
||
|
case 'pdf':
|
||
|
return ("." . $m[1]);
|
||
|
default:
|
||
|
return (".dat");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function phimail_service_userID($name = 'phimail-service')
|
||
|
{
|
||
|
$sql = "SELECT id FROM users WHERE username=?";
|
||
|
if (
|
||
|
($r = sqlStatementNoLog($sql, array($name))) === false ||
|
||
|
($u = sqlFetchArray($r)) === false
|
||
|
) {
|
||
|
$user = 1; //default if we don't have a service user
|
||
|
} else {
|
||
|
$user = $u['id'];
|
||
|
}
|
||
|
|
||
|
return ($user);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given an IsAcceptedFileFilterEvent from our isWhitelist function we check to make sure that we allow
|
||
|
* the mime type of the Direct message attachment that was sent by the server.
|
||
|
*
|
||
|
* @param IsAcceptedFileFilterEvent $event The event with the mimetype to check if we allow it or not
|
||
|
* @return IsAcceptedFileFilterEvent
|
||
|
*/
|
||
|
function phimail_allow_document_mimetype(IsAcceptedFileFilterEvent $event)
|
||
|
{
|
||
|
global $phimail_direct_message_check_allowed_mimetype;
|
||
|
$isAllowedFile = $event->isAllowedFile();
|
||
|
if (!$isAllowedFile) {
|
||
|
// we used to only bypass if the Direct mime type matched with what comes through in the event.
|
||
|
// This fails though if there are multiple possible mime types such as application/xml vs text/xml and the Direct
|
||
|
// mime type differs from the local OS detection. We will just bypass the mime check alltogether.
|
||
|
$event->setAllowedFile(true);
|
||
|
}
|
||
|
return $event;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Registers an attachment or non-text message file using the existing Document structure
|
||
|
*
|
||
|
* @return Array(doc_id,URL) of the file as stored in documents table, false = failure
|
||
|
*/
|
||
|
function phimail_store($name, $mime_type, $fn)
|
||
|
{
|
||
|
global $phimail_direct_message_check_allowed_mimetype;
|
||
|
|
||
|
$allowMimeTypeFunction = 'phimail_allow_document_mimetype';
|
||
|
// we bypass the whitelisting JUST for phimail documents
|
||
|
if (isset($GLOBALS['kernel'])) {
|
||
|
$GLOBALS['kernel']->getEventDispatcher()
|
||
|
->addListener(IsAcceptedFileFilterEvent::EVENT_FILTER_IS_ACCEPTED_FILE, $allowMimeTypeFunction);
|
||
|
}
|
||
|
// Collect phimail user id
|
||
|
$user = phimail_service_userID();
|
||
|
try {
|
||
|
// Import the document
|
||
|
$phimail_direct_message_check_allowed_mimetype = $mime_type;
|
||
|
$filesize = filesize($fn);
|
||
|
$return = addNewDocument($name, $mime_type, $fn, 0, $filesize, $user, 'direct');
|
||
|
if (is_array($return)) {
|
||
|
$return['filesize'] = $filesize;
|
||
|
}
|
||
|
} catch (\Exception $exception) {
|
||
|
(new SystemLogger())->errorLogCaller($exception->getMessage(), ['name' => $name, 'mime_type' => $mime_type, 'fn' => $fn]);
|
||
|
phimail_logit(0, "problem storing attachment in OpenEMR");
|
||
|
$return = false;
|
||
|
} finally {
|
||
|
$phimail_direct_message_check_allowed_mimetype = null;
|
||
|
// There shouldn't be another request in the system to add a document, but for security sake we will prevent code
|
||
|
// after this from bypassing the whitelist filter
|
||
|
if (isset($GLOBALS['kernel'])) {
|
||
|
$GLOBALS['kernel']->getEventDispatcher()
|
||
|
->removeListener(IsAcceptedFileFilterEvent::EVENT_FILTER_IS_ACCEPTED_FILE, $allowMimeTypeFunction);
|
||
|
}
|
||
|
// Remove the temporary file
|
||
|
@unlink($fn);
|
||
|
}
|
||
|
|
||
|
// Return the result
|
||
|
return $return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send an error notification or other alert to the notification address specified in globals.
|
||
|
* (notification email function modified from interface/drugs/dispense_drug.php)
|
||
|
*
|
||
|
* @return true if notification successfully sent, false otherwise
|
||
|
*/
|
||
|
function phimail_notify($subj, $body)
|
||
|
{
|
||
|
$recipient = $GLOBALS['practice_return_email_path'];
|
||
|
if (empty($recipient)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$mail = new PHPMailer();
|
||
|
$mail->From = $recipient;
|
||
|
$mail->FromName = 'phiMail Gateway';
|
||
|
$mail->isMail();
|
||
|
$mail->Host = "localhost";
|
||
|
$mail->Mailer = "mail";
|
||
|
$mail->Body = $body;
|
||
|
$mail->Subject = $subj;
|
||
|
$mail->AddAddress($recipient);
|
||
|
return ($mail->Send());
|
||
|
}
|