Files
BE_IBL/scripts/send_email.php
2026-05-24 21:29:09 +07:00

363 lines
11 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/**
* Email queue processor — reads t_send_email (Status=S, IsActive=Y),
* downloads PDF attachments from T_SendEmailReports URLs,
* encrypts PDFs with patient DOB password for PATIENT type,
* sends via SMTP STARTTLS, updates status on completion.
*
* Usage:
* php scripts/send_email.php
* php scripts/send_email.php --id=17 (process single email)
* php scripts/send_email.php --dry-run (skip actual send + DB update)
*/
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'sasone102938');
define('DB_NAME', 'one_lab');
define('SMTP_PORT', 587);
define('EMAIL_SUBJECT', 'Hasil Pemeriksaan Laboratorium');
// ─── CLI args ────────────────────────────────────────────────────────────────
$dry_run = in_array('--dry-run', $argv);
$only_id = null;
foreach ($argv as $arg) {
if (preg_match('/^--id=(\d+)$/', $arg, $m)) {
$only_id = (int) $m[1];
}
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
function log_msg(string $msg): void
{
echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
}
function download_pdf(string $url)
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_SSL_VERIFYPEER => false,
]);
$data = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
log_msg(" curl error: $err");
return false;
}
if ($http_code !== 200 || !$data) {
log_msg(" HTTP $http_code, empty=" . (empty($data) ? 'yes' : 'no'));
return false;
}
return $data;
}
function encrypt_pdf(string $input_path, string $password)
{
$output_path = $input_path . '_enc.pdf';
$cmd = sprintf(
'gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -dEncryptionR=3 -dKeyLength=128 '
. '-sOwnerPassword=%s -sUserPassword=%s -sOutputFile=%s %s 2>/dev/null',
escapeshellarg($password),
escapeshellarg($password),
escapeshellarg($output_path),
escapeshellarg($input_path)
);
exec($cmd, $out, $ret);
if ($ret !== 0 || !file_exists($output_path) || filesize($output_path) === 0) {
return false;
}
return $output_path;
}
// ─── SMTP ─────────────────────────────────────────────────────────────────────
function smtp_read($socket): string
{
$line = '';
while (!feof($socket)) {
$ch = fread($socket, 1);
if ($ch === false) break;
$line .= $ch;
if (substr($line, -1) === "\n") break;
}
return rtrim($line);
}
function smtp_cmd($socket, string $cmd): string
{
fwrite($socket, $cmd . "\r\n");
return smtp_read($socket);
}
/** Drain multi-line SMTP responses (e.g. 250-xxx ... 250 OK) */
function smtp_read_all($socket, string $first): string
{
$last = $first;
while (strlen($last) >= 4 && $last[3] === '-') {
$last = smtp_read($socket);
}
return $last;
}
/**
* @param array $smtp ['server', 'username', 'password']
* @param string $from_addr
* @param string $from_name
* @param string $to
* @param string $cc empty string = no CC
* @param string $subject
* @param string $body_html
* @param array $attachments [['path'=>..., 'name'=>...], ...]
* @return string|null null on success, error message on failure
*/
function send_email(
array $smtp,
string $from_addr,
string $from_name,
string $to,
string $cc,
string $subject,
string $body_html,
array $attachments = []
) {
$socket = @fsockopen('tcp://' . $smtp['server'], SMTP_PORT, $errno, $errstr, 30);
if (!$socket) {
return "Connection to {$smtp['server']}:" . SMTP_PORT . " failed: $errstr ($errno)";
}
stream_set_timeout($socket, 30);
smtp_read($socket); // greeting
$resp = smtp_cmd($socket, 'EHLO localhost');
smtp_read_all($socket, $resp);
$resp = smtp_cmd($socket, 'STARTTLS');
if (strpos($resp, '220') === false) {
fclose($socket);
return "STARTTLS rejected: $resp";
}
if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
fclose($socket);
return "TLS upgrade failed";
}
$resp = smtp_cmd($socket, 'EHLO localhost');
smtp_read_all($socket, $resp);
smtp_cmd($socket, 'AUTH LOGIN');
smtp_cmd($socket, base64_encode($smtp['username']));
$resp = smtp_cmd($socket, base64_encode($smtp['password']));
if (strpos($resp, '235') === false) {
fclose($socket);
return "AUTH failed: $resp";
}
smtp_cmd($socket, "MAIL FROM:<{$from_addr}>");
smtp_cmd($socket, "RCPT TO:<{$to}>");
if ($cc !== '') {
smtp_cmd($socket, "RCPT TO:<{$cc}>");
}
smtp_cmd($socket, 'DATA');
$boundary = md5(uniqid((string) rand(), true));
$msg = "From: {$from_name} <{$from_addr}>\r\n";
$msg .= "To: {$to}\r\n";
if ($cc !== '') {
$msg .= "Cc: {$cc}\r\n";
}
$msg .= 'Subject: =?UTF-8?B?' . base64_encode($subject) . "?=\r\n";
$msg .= "MIME-Version: 1.0\r\n";
$msg .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n";
$msg .= "\r\n";
// HTML body part
$msg .= "--{$boundary}\r\n";
$msg .= "Content-Type: text/html; charset=UTF-8\r\n";
$msg .= "Content-Transfer-Encoding: base64\r\n\r\n";
$msg .= chunk_split(base64_encode($body_html)) . "\r\n";
// PDF attachments
foreach ($attachments as $att) {
$raw = file_get_contents($att['path']);
if ($raw === false) continue;
$msg .= "--{$boundary}\r\n";
$msg .= "Content-Type: application/pdf\r\n";
$msg .= "Content-Transfer-Encoding: base64\r\n";
$msg .= "Content-Disposition: attachment; filename=\"{$att['name']}\"\r\n\r\n";
$msg .= chunk_split(base64_encode($raw)) . "\r\n";
}
$msg .= "--{$boundary}--\r\n";
// RFC 2821: lines beginning with "." must be dot-stuffed
$msg = preg_replace('/^\.$/m', '..', $msg);
fwrite($socket, $msg . "\r\n.\r\n");
$resp = smtp_read($socket);
smtp_cmd($socket, 'QUIT');
fclose($socket);
return (strpos($resp, '250') !== false) ? null : "DATA rejected: $resp";
}
// ─── Main ─────────────────────────────────────────────────────────────────────
try {
$pdo = new PDO(
'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8',
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
} catch (PDOException $e) {
log_msg('DB connect failed: ' . $e->getMessage());
exit(1);
}
// Email config
$cfg = $pdo->query(
"SELECT * FROM m_emailconfig WHERE M_EmailConfigIsActive = 'Y' LIMIT 1"
)->fetch(PDO::FETCH_ASSOC);
if (!$cfg) {
log_msg('No active email config found');
exit(1);
}
$smtp = [
'server' => $cfg['M_EmailConfigServer'],
'username' => $cfg['M_EmailConfigUsername'],
'password' => $cfg['M_EmailConfigPassword'],
];
$from_addr = $cfg['M_EmailConfigUsername'];
$from_name = $cfg['M_EmailConfigSender'];
$cc = (string) ($cfg['M_EmailConfigCc'] ?? '');
$max_retry = (int) $cfg['M_EmailConfigMaxRetry'];
// Pending queue
$where_id = $only_id ? 'AND e.T_SendEmailID = ' . $only_id : '';
$sql = "
SELECT
e.T_SendEmailID,
e.T_SendEmailRecepient,
e.T_SendEmailRecepientType,
e.T_SendEmailPatientName,
e.T_SendEmailNarratives,
e.T_SendEmailReports,
e.T_SendEmailCount,
p.M_PatientDOB
FROM t_send_email e
LEFT JOIN t_orderheader oh
ON oh.T_OrderHeaderID = e.T_SendEmailT_OrderHeaderID
LEFT JOIN m_patient p
ON p.M_PatientID = oh.T_OrderHeaderM_PatientID
WHERE e.T_SendEmailIsActive = 'Y'
AND e.T_SendEmailStatus = 'S'
AND e.T_SendEmailCount < {$max_retry}
{$where_id}
ORDER BY e.T_SendEmailID ASC
";
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
log_msg('Found ' . count($rows) . ' pending email(s)' . ($dry_run ? ' [DRY RUN]' : ''));
foreach ($rows as $row) {
$id = (int) $row['T_SendEmailID'];
$recipient = $row['T_SendEmailRecepient'];
$rec_type = $row['T_SendEmailRecepientType'];
$body_html = $row['T_SendEmailNarratives'] ?? '';
$dob = $row['M_PatientDOB'] ?? '';
$password = str_replace('-', '', $dob);
$reports = json_decode($row['T_SendEmailReports'] ?? '[]', true) ?: [];
log_msg("Processing ID {$id}{$recipient} ({$rec_type})");
$attachments = [];
$tmp_files = [];
foreach ($reports as $idx => $url) {
log_msg(" Downloading attachment " . ($idx + 1) . ": {$url}");
$pdf = download_pdf($url);
if ($pdf === false) {
log_msg(" Download failed, skipping");
continue;
}
$tmp = tempnam(sys_get_temp_dir(), 'ibl_email_') . '.pdf';
file_put_contents($tmp, $pdf);
$tmp_files[] = $tmp;
$final = $tmp;
if ($rec_type === 'PATIENT' && $password !== '') {
log_msg(" Encrypting PDF (password: {$password})");
$enc = encrypt_pdf($tmp, $password);
if ($enc) {
$tmp_files[] = $enc;
$final = $enc;
} else {
log_msg(" Encryption failed — attaching unencrypted");
}
}
$attachments[] = [
'path' => $final,
'name' => 'hasil_lab_' . ($idx + 1) . '.pdf',
];
}
if ($dry_run) {
log_msg(" [DRY RUN] would send to {$recipient} with " . count($attachments) . " attachment(s)");
foreach ($tmp_files as $f) {
if (file_exists($f)) unlink($f);
}
continue;
}
$err = send_email(
$smtp,
$from_addr,
$from_name,
$recipient,
$cc,
EMAIL_SUBJECT,
$body_html,
$attachments
);
foreach ($tmp_files as $f) {
if (file_exists($f)) unlink($f);
}
if ($err === null) {
log_msg(" Sent OK");
$pdo->prepare("
UPDATE t_send_email
SET T_SendEmailStatus = 'D',
T_SendEmailCount = T_SendEmailCount + 1,
T_SendEmailReceived = NOW(),
T_SendEmailLastUpdated = NOW()
WHERE T_SendEmailID = ?
")->execute([$id]);
} else {
log_msg(" Error: {$err}");
$pdo->prepare("
UPDATE t_send_email
SET T_SendEmailCount = T_SendEmailCount + 1,
T_SendEmailResponse = ?,
T_SendEmailLastUpdated = NOW()
WHERE T_SendEmailID = ?
")->execute([$err, $id]);
}
}
log_msg('Done');