$v !== '')); } function sendJSON(mixed $data, int $code = 200): void { http_response_code($code); echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function sendSuccess(mixed $data = null, string $message = 'OK', int $code = 200): void { sendJSON(['status' => 'success', 'message' => $message, 'data' => $data], $code); } function sendError(string $message, int $code = 400): void { sendJSON(['status' => 'error', 'message' => $message], $code); } function requireWriteAuth(string $method): void { // Deprecated: Menggunakan checkRBAC() secara global untuk semua request. } function checkRBAC(): void { if (PHP_SAPI === 'cli') { return; } $script = basename($_SERVER['SCRIPT_FILENAME']); if (in_array($script, ['login.php', 'logout.php', 'me.php'], true)) { return; } if (session_status() === PHP_SESSION_NONE) { ini_set('session.cookie_httponly', 1); ini_set('session.use_only_cookies', 1); session_start(); } if (!isset($_SESSION['user_id'])) { sendError('Unauthorized - Silakan login terlebih dahulu', 401); } $role = $_SESSION['role'] ?? 'guest'; $method = getMethod(); if ($method === 'GET') { // Semua role diizinkan membaca data (GET) return; } // Operasi Tulis (POST, PUT, DELETE) if ($role === 'admin') { // Admin diizinkan melakukan semua aksi tulis return; } if ($role === 'petugas_lapangan') { $user_ri_id = isset($_SESSION['rumah_ibadah_id']) ? (int)$_SESSION['rumah_ibadah_id'] : 0; if ($user_ri_id <= 0) { sendError('Forbidden - Akun petugas lapangan tidak dikaitkan dengan rumah ibadah manapun', 403); } if ($method === 'DELETE') { sendError('Forbidden - Petugas Lapangan tidak diizinkan menghapus data', 403); } if ($script === 'rumah_ibadah.php') { if ($method === 'PUT') { $req_id = isset($_GET['id']) ? (int)$_GET['id'] : 0; if ($req_id !== $user_ri_id) { sendError('Forbidden - Petugas Lapangan hanya dapat mengedit rumah ibadah mereka sendiri', 403); } return; // Allowed } sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada rumah ibadah', 403); } if ($script === 'penduduk_miskin.php') { if ($method === 'POST') { if (isset($_GET['action']) && $_GET['action'] === 'recalc_all') { sendError('Forbidden - Petugas Lapangan tidak dapat memperbarui proximity masal', 403); } $body = getBody(); $lat = isset($body['latitude']) ? (float)$body['latitude'] : null; $lng = isset($body['longitude']) ? (float)$body['longitude'] : null; if ($lat === null || $lng === null) { sendError('Field latitude dan longitude wajib diisi'); } $db = getDB(); $stmt = $db->prepare("SELECT latitude, longitude, radius_meter FROM rumah_ibadah WHERE id = ?"); $stmt->bind_param('i', $user_ri_id); $stmt->execute(); $ri = $stmt->get_result()->fetch_assoc(); if (!$ri) { sendError('Rumah ibadah petugas tidak ditemukan', 404); } $dist = haversine_distance((float)$ri['latitude'], (float)$ri['longitude'], $lat, $lng); if ($dist > (int)$ri['radius_meter']) { sendError('Forbidden - Titik koordinat penduduk miskin di luar radius jangkauan rumah ibadah Anda', 403); } return; // Allowed } if ($method === 'PUT') { $req_id = isset($_GET['id']) ? (int)$_GET['id'] : 0; if ($req_id <= 0) { sendError('ID penduduk miskin wajib disertakan'); } $db = getDB(); $stmtCheck = $db->prepare("SELECT rumah_ibadah_id FROM penduduk_miskin WHERE id = ?"); $stmtCheck->bind_param('i', $req_id); $stmtCheck->execute(); $pm = $stmtCheck->get_result()->fetch_assoc(); if (!$pm) { sendError('Penduduk miskin tidak ditemukan', 404); } if ($pm['rumah_ibadah_id'] === null || (int)$pm['rumah_ibadah_id'] !== $user_ri_id) { sendError('Forbidden - Penduduk miskin ini tidak berada di wilayah rumah ibadah Anda', 403); } $body = getBody(); $lat = isset($body['latitude']) ? (float)$body['latitude'] : null; $lng = isset($body['longitude']) ? (float)$body['longitude'] : null; if ($lat === null || $lng === null) { sendError('Field latitude dan longitude wajib diisi'); } $stmt = $db->prepare("SELECT latitude, longitude, radius_meter FROM rumah_ibadah WHERE id = ?"); $stmt->bind_param('i', $user_ri_id); $stmt->execute(); $ri = $stmt->get_result()->fetch_assoc(); if (!$ri) { sendError('Rumah ibadah petugas tidak ditemukan', 404); } $dist = haversine_distance((float)$ri['latitude'], (float)$ri['longitude'], $lat, $lng); if ($dist > (int)$ri['radius_meter']) { sendError('Forbidden - Titik koordinat baru di luar radius jangkauan rumah ibadah Anda', 403); } return; // Allowed } sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada penduduk miskin', 403); } if ($script === 'log_bantuan.php') { if ($method === 'POST') { $body = getBody(); $req_pm_id = isset($body['penduduk_miskin_id']) ? (int)$body['penduduk_miskin_id'] : 0; $req_ri_id = isset($body['rumah_ibadah_id']) ? (int)$body['rumah_ibadah_id'] : 0; if ($req_ri_id !== $user_ri_id) { sendError('Forbidden - Anda hanya dapat mencatat log bantuan dari rumah ibadah Anda sendiri', 403); } $db = getDB(); $stmtPm = $db->prepare("SELECT rumah_ibadah_id, status_proximity FROM penduduk_miskin WHERE id = ?"); $stmtPm->bind_param('i', $req_pm_id); $stmtPm->execute(); $pm = $stmtPm->get_result()->fetch_assoc(); if (!$pm) { sendError('Penduduk miskin tidak ditemukan', 404); } if ($pm['rumah_ibadah_id'] === null || (int)$pm['rumah_ibadah_id'] !== $user_ri_id || $pm['status_proximity'] !== 'dalam_radius') { sendError('Forbidden - Penduduk miskin tidak berada di dalam radius rumah ibadah Anda', 403); } return; // Allowed } sendError('Forbidden - Aksi tidak diizinkan untuk petugas lapangan pada log bantuan', 403); } sendError('Forbidden', 403); } if ($role === 'guest') { sendError('Forbidden - Guest hanya memiliki akses lihat (read-only)', 403); } sendError('Forbidden', 403); } // Jalankan pemeriksaan CORS dan RBAC secara global saat file di-load setCORSHeaders(); checkRBAC(); function executeOrFail(mysqli_stmt $stmt, string $defaultMessage = 'Operasi database gagal'): void { if ($stmt->execute()) { return; } $errno = $stmt->errno; $error = $stmt->error; error_log('DB stmt error [' . $errno . ']: ' . $error); if ($errno === 1062) { sendError('Data duplikat terdeteksi', 409); } if ($errno === 1451 || $errno === 1452) { sendError('Relasi data tidak valid', 422); } sendError($defaultMessage, 500); } function getBody(): array { $raw = file_get_contents('php://input'); $data = json_decode($raw, true); return is_array($data) ? $data : []; } function getMethod(): string { return strtoupper($_SERVER['REQUEST_METHOD']); } function haversine_distance(float $lat1, float $lng1, float $lat2, float $lng2): float { $R = 6371000; // radius bumi dalam meter $dLat = deg2rad($lat2 - $lat1); $dLng = deg2rad($lng2 - $lng1); $a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2; return $R * 2 * atan2(sqrt($a), sqrt(1-$a)); }