259 lines
9.6 KiB
PHP
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));
|
|
} |