diff --git a/scripts/send_email.php b/scripts/send_email.php new file mode 100755 index 00000000..3a769813 --- /dev/null +++ b/scripts/send_email.php @@ -0,0 +1,362 @@ +#!/usr/bin/env php + 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): string|false +{ + $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 = [] +): ?string { + $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');