PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, DB_USER, DB_PASS, $pdoOptions); } catch (PDOException $e) { http_response_code(500); header('Content-Type: application/json'); // Jangan expose detail error di produksi $errMsg = (defined('APP_ENV') && APP_ENV === 'development') ? $e->getMessage() : 'Hubungi administrator sistem.'; echo json_encode([ 'success' => false, 'message' => 'Koneksi database gagal: ' . DB_NAME . '@' . DB_HOST . ' tidak ditemukan. Pastikan database sudah dibuat dan nama database benar.', 'error' => $errMsg ]); exit; } // ============================================================ // Helper: Response JSON // ============================================================ function jsonResponse(bool $success, string $message, array $data = [], int $httpCode = 200): void { http_response_code($httpCode); header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); echo json_encode(array_merge(['success' => $success, 'message' => $message], $data), JSON_UNESCAPED_UNICODE); exit; } // ============================================================ // Helper: Sanitasi & Validasi // ============================================================ function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } function sanitizeStr(?string $v, int $max = 255): string { return mb_substr(trim($v ?? ''), 0, $max); } function validateNIK(string $nik): bool { return preg_match('/^\d{16}$/', $nik) === 1; } function validateCoord(string $val, string $type = 'lat'): bool { $f = (float)$val; return $type === 'lat' ? ($f >= -90 && $f <= 90) : ($f >= -180 && $f <= 180); } // ============================================================ // Helper: Upload File // ============================================================ function uploadFile(array $file, string $destDir, array $allowedExt = null): ?string { if ($allowedExt === null) $allowedExt = ALLOWED_FILE_EXT; if ($file['error'] !== UPLOAD_ERR_OK) return null; if ($file['size'] > MAX_FILE_SIZE) throw new RuntimeException('Ukuran file melebihi 5MB.'); $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($ext, $allowedExt, true)) throw new RuntimeException("Format file .$ext tidak diizinkan."); $filename = uniqid('img_', true) . '.' . $ext; $dest = rtrim($destDir, '/') . '/' . $filename; if (!move_uploaded_file($file['tmp_name'], $dest)) throw new RuntimeException('Gagal menyimpan file.'); return $filename; } // ============================================================ // Helper: Log Aktivitas // ============================================================ function logAktivitas(PDO $pdo, ?int $userId, string $aksi, string $tabel = '', string $recordId = '', $dataLama = null, $dataBaru = null): void { try { $stmt = $pdo->prepare(" INSERT INTO log_aktivitas (user_id, aksi, tabel, record_id, data_lama, data_baru, ip_address, user_agent) VALUES (:u, :a, :t, :r, :dl, :db, :ip, :ua) "); $stmt->execute([ ':u' => $userId, ':a' => $aksi, ':t' => $tabel, ':r' => $recordId, ':dl' => $dataLama ? json_encode($dataLama, JSON_UNESCAPED_UNICODE) : null, ':db' => $dataBaru ? json_encode($dataBaru, JSON_UNESCAPED_UNICODE) : null, ':ip' => $_SERVER['REMOTE_ADDR'] ?? null, ':ua' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255), ]); } catch (Exception) { /* log gagal tidak menghentikan proses */ } } // ============================================================ // Validasi Logika Pendidikan vs Umur // ============================================================ function validasiPendidikanUmur(int $umur, string $pendidikan, string $statusPendidikan): array { $warnings = []; $errors = []; if ($umur >= 7 && $umur <= 12) { if ($statusPendidikan !== 'sekolah') { $warnings[] = "Anak usia $umur tahun semestinya masih sekolah SD."; } } elseif ($umur >= 13 && $umur <= 15) { if ($statusPendidikan !== 'sekolah') { $warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMP."; } } elseif ($umur >= 16 && $umur <= 18) { if ($statusPendidikan !== 'sekolah') { $warnings[] = "Anak usia $umur tahun semestinya masih sekolah SMA/SMK."; } } // Umur > 18: bebas, boleh tidak sekolah, boleh pelatihan return ['warnings' => $warnings, 'errors' => $errors]; } // ============================================================ // Prioritas Bantuan // ============================================================ function hitungPrioritasBantuan(string $statusEkonomi, string $kondisiKesehatan, bool $belumPernahDibantu): string { if ($statusEkonomi === 'miskin' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) { return 'KRITIS'; } if ($statusEkonomi === 'miskin' && $belumPernahDibantu) { return 'TINGGI'; } if ($statusEkonomi === 'miskin') { return 'SEDANG'; } if ($statusEkonomi === 'rentan' && in_array($kondisiKesehatan, ['sakit_parah', 'disabilitas'])) { return 'SEDANG'; } return 'RENDAH'; }