#!/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; } // Load ibl_encryptor untuk decrypt PII sebelum fetch PDF dari BIRT define('BASEPATH', true); $_env_file = __DIR__ . '/../.env'; if (file_exists($_env_file)) { foreach (file($_env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $_l) { if (strpos(trim($_l), '#') === 0) continue; [$_k, $_v] = array_map('trim', explode('=', $_l, 2)); if ($_k !== '') $_ENV[$_k] = $_v; } } require __DIR__ . '/../application/libraries/Ibl_encryptor.php'; $_enc = new Ibl_encryptor(); // Populate patient_print_cache sebelum fetch PDF dari BIRT function populate_birt_cache(PDO $pdo, $enc, string $birt_url): ?int { parse_str(parse_url($birt_url, PHP_URL_QUERY) ?? '', $params); $order_id = intval($params['PID'] ?? 0); if (!$order_id) return null; $patient = $pdo->query( "SELECT M_PatientID, M_PatientName_enc, M_PatientDOB_enc, M_PatientHP_enc, M_PatientEmail_enc, M_PatientDOB FROM t_orderheader JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID WHERE T_OrderHeaderID = {$order_id} LIMIT 1" )->fetch(PDO::FETCH_ASSOC); if (!$patient) return null; $addr = $pdo->query( "SELECT M_PatientAddressDescription_enc FROM m_patientaddress WHERE M_PatientAddressM_PatientID = {$patient['M_PatientID']} AND M_PatientAddressIsActive = 'Y' AND M_PatientAddressNote = 'Utama' LIMIT 1" )->fetch(PDO::FETCH_ASSOC); $name = $enc->decrypt($patient['M_PatientName_enc'] ?? '') ?? ''; $dob = $enc->decrypt($patient['M_PatientDOB_enc'] ?? '') ?? date('d-m-Y', strtotime($patient['M_PatientDOB'] ?? 'now')); $hp = $enc->decrypt($patient['M_PatientHP_enc'] ?? '') ?? ''; $email = $enc->decrypt($patient['M_PatientEmail_enc']?? '') ?? ''; $address = $enc->decrypt($addr['M_PatientAddressDescription_enc'] ?? '') ?? ''; $pdo->exec("DELETE FROM patient_print_cache WHERE ppc_order_id = {$order_id} OR ppc_created < NOW() - INTERVAL 5 MINUTE"); $stmt = $pdo->prepare("INSERT INTO patient_print_cache (ppc_order_id, ppc_patient_id, ppc_name, ppc_dob, ppc_hp, ppc_email, ppc_address) VALUES (?,?,?,?,?,?,?)"); $stmt->execute([$order_id, $patient['M_PatientID'], $name, $dob, $hp, $email, $address]); return (int)$pdo->lastInsertId(); } function delete_birt_cache(PDO $pdo, ?int $cache_id): void { if ($cache_id) $pdo->exec("DELETE FROM patient_print_cache WHERE ppc_id = {$cache_id}"); } 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] ); $pdo_log = new PDO( 'mysql:host=' . DB_HOST . ';dbname=' . DB_LOG_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_SendEmailT_OrderHeaderID, 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']; $order_id = (int) $row['T_SendEmailT_OrderHeaderID']; $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 => $entry) { // Support both formats: // old: ["http://..."] // new: [{"id":"1","url":"http://...","result":"LAB"}] if (is_array($entry)) { $url = $entry['url'] ?? ''; $result = $entry['result'] ?? ('hasil_lab_' . ($idx + 1)); } else { $url = (string) $entry; $result = 'hasil_lab_' . ($idx + 1); } if (empty($url)) continue; log_msg(" Downloading attachment " . ($idx + 1) . " [{$result}]: {$url}"); $cache_id = populate_birt_cache($pdo, $GLOBALS['_enc'], $url); $pdf = download_pdf($url); delete_birt_cache($pdo, $cache_id); if ($pdf === false) { log_msg(" Download failed, skipping"); continue; } if (substr($pdf, 0, 4) !== '%PDF') { log_msg(" Not a PDF (got HTML/error response), 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"); } } $safe_result = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $result); $attachments[] = [ 'path' => $final, 'name' => $safe_result . '.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; } // Lock: tandai sedang dikirim agar tidak bisa di-trigger ulang dari UI $pdo->prepare(" UPDATE t_send_email SET T_SendEmailStatus = 'P', T_SendEmailLastUpdated = NOW() WHERE T_SendEmailID = ? ")->execute([$id]); $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); } $group_result_names = array_filter(array_column( array_filter($reports, 'is_array'), 'result' )); if ($err === null) { log_msg(" Sent OK → R"); $pdo->prepare(" UPDATE t_send_email SET T_SendEmailStatus = 'R', T_SendEmailCount = T_SendEmailCount + 1, T_SendEmailReceived = NOW(), T_SendEmailLastUpdated = NOW() WHERE T_SendEmailID = ? ")->execute([$id]); $pdo_log->prepare(" INSERT INTO t_send_email_log (T_SendEmailLogT_OrderHeaderID, T_SendEmailLogRecepient, T_SendEmailLogStatus, T_SendEmailLogResponse, T_SendEmailLogGroup_ResultName, T_SendEmailLogJson, T_SendEmailLogCreated, T_SendEmailLogCreatedUserID) VALUES (?, ?, 'R', NULL, ?, ?, NOW(), 0) ")->execute([ $order_id, $recipient, implode(', ', $group_result_names), json_encode($row), ]); } else { $new_count = (int)$row['T_SendEmailCount'] + 1; $new_status = ($new_count >= $max_retry) ? 'E' : 'S'; log_msg(" Error (retry {$new_count}/{$max_retry}) → {$new_status}: {$err}"); $pdo->prepare(" UPDATE t_send_email SET T_SendEmailStatus = ?, T_SendEmailCount = T_SendEmailCount + 1, T_SendEmailResponse = ?, T_SendEmailLastUpdated = NOW() WHERE T_SendEmailID = ? ")->execute([$new_status, $err, $id]); if ($new_status === 'E') { $pdo_log->prepare(" INSERT INTO t_send_email_log (T_SendEmailLogT_OrderHeaderID, T_SendEmailLogRecepient, T_SendEmailLogStatus, T_SendEmailLogResponse, T_SendEmailLogGroup_ResultName, T_SendEmailLogJson, T_SendEmailLogCreated, T_SendEmailLogCreatedUserID) VALUES (?, ?, 'E', ?, ?, ?, NOW(), 0) ")->execute([ $order_id, $recipient, $err, implode(', ', $group_result_names), json_encode($row), ]); } } } log_msg('Done');