#!/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) { $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');