Files
2026-06-11 01:50:37 +07:00

259 lines
9.6 KiB
PHP

<?php
// api/helpers/response.php
function setCORSHeaders(): void {
header('Content-Type: application/json; charset=utf-8');
header('Vary: Origin');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Token');
header('X-Content-Type-Options: nosniff');
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowed = false;
if ($origin !== '') {
$origin = trim($origin);
$allowedOrigins = parseAllowedOrigins(getenv('CORS_ALLOWED_ORIGINS') ?: '');
if (!empty($allowedOrigins)) {
$allowed = in_array($origin, $allowedOrigins, true);
} else {
$allowed = (bool)preg_match('/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/', $origin);
}
if ($allowed) {
header('Access-Control-Allow-Origin: ' . $origin);
}
}
if (getMethod() === 'OPTIONS') {
http_response_code(($origin !== '' && !$allowed) ? 403 : 204);
exit;
}
}
function parseAllowedOrigins(string $raw): array {
$parts = array_map('trim', explode(',', $raw));
return array_values(array_filter($parts, static fn ($v) => $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));
}