$status, 'message' => $message, 'data' => $data ], JSON_UNESCAPED_UNICODE); exit; } function require_post() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { json_response('err', 'Method harus POST.', [], 405); } } function is_logged_in() { return isset($_SESSION['user_id'], $_SESSION['role']); } function require_login($json = false) { if (!is_logged_in()) { if ($json) json_response('err', 'Silakan login terlebih dahulu.', [], 401); header('Location: login.php'); exit; } } function current_user() { global $koneksi; if (!is_logged_in()) return null; $id = (int) $_SESSION['user_id']; $stmt = $koneksi->prepare("SELECT id, nama, username, role, status_akun FROM users WHERE id=? LIMIT 1"); $stmt->bind_param('i', $id); $stmt->execute(); $res = $stmt->get_result(); return $res->fetch_assoc(); } function has_role($roles) { if (!is_array($roles)) $roles = [$roles]; return is_logged_in() && in_array($_SESSION['role'], $roles, true); } function require_role($roles, $json = false) { if (!has_role($roles)) { if ($json) json_response('err', 'Akses ditolak. Role tidak sesuai.', [], 403); http_response_code(403); echo '

403 - Akses ditolak

'; exit; } } function post($key, $default = '') { return isset($_POST[$key]) ? trim((string) $_POST[$key]) : $default; } function getv($key, $default = '') { return isset($_GET[$key]) ? trim((string) $_GET[$key]) : $default; } function clean_text($str, $max = 255) { $str = trim((string) $str); if (mb_strlen($str) > $max) $str = mb_substr($str, 0, $max); return $str; } function valid_enum($value, $allowed) { return in_array($value, $allowed, true); } function valid_latlng($lat, $lng) { return is_numeric($lat) && is_numeric($lng) && $lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180; } function haversine_meter($lat1, $lon1, $lat2, $lon2) { $R = 6371000; // meter $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2); $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); return $R * $c; } function stmt_bind($stmt, $types, $params) { if ($types === '' || empty($params)) return true; $refs = []; $refs[] = $types; foreach ($params as $k => $v) { $refs[] = &$params[$k]; } return call_user_func_array([$stmt, 'bind_param'], $refs); } function fetch_all_stmt($stmt) { $stmt->execute(); $result = $stmt->get_result(); $rows = []; while ($row = $result->fetch_assoc()) $rows[] = $row; return $rows; } function build_in_condition($column, $values, &$types, &$params) { $ids = []; foreach ((array)$values as $v) { $iv = (int)$v; if ($iv > 0) $ids[] = $iv; } $ids = array_values(array_unique($ids)); if (empty($ids)) return '1=0'; $placeholders = implode(',', array_fill(0, count($ids), '?')); $types .= str_repeat('i', count($ids)); foreach ($ids as $id) $params[] = $id; return "$column IN ($placeholders)"; } function get_admin_rumah_ibadah_list($user_id = null, $only_diterima = true) { global $koneksi; if ($user_id === null) $user_id = (int) $_SESSION['user_id']; $sql = "SELECT * FROM rumah_ibadah WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui'"; if ($only_diterima) $sql .= " AND status_pengajuan='diterima'"; $sql .= " ORDER BY id ASC"; $stmt = $koneksi->prepare($sql); $stmt->bind_param('i', $user_id); return fetch_all_stmt($stmt); } function get_admin_rumah_ibadah_ids($user_id = null, $only_diterima = true) { $rows = get_admin_rumah_ibadah_list($user_id, $only_diterima); return array_map('intval', array_column($rows, 'id')); } function get_admin_rumah_ibadah($user_id = null) { // Dipertahankan untuk kompatibilitas lama. Untuk logic baru, pakai get_admin_rumah_ibadah_list()/ids(). $rows = get_admin_rumah_ibadah_list($user_id, true); return $rows[0] ?? null; } function is_admin_rumah_ibadah_id($rumah_ibadah_id, $user_id = null, $only_diterima = true) { if ($user_id === null) $user_id = (int) $_SESSION['user_id']; $ids = get_admin_rumah_ibadah_ids($user_id, $only_diterima); return in_array((int)$rumah_ibadah_id, $ids, true); } function get_nearest_rumah_ibadah($lat, $lng, $max_meter = 300, $admin_user_id = null) { global $koneksi; $rows = []; if ($admin_user_id !== null) { $stmt = $koneksi->prepare("SELECT * FROM rumah_ibadah WHERE user_id=? AND status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'"); $admin_user_id = (int)$admin_user_id; $stmt->bind_param('i', $admin_user_id); $rumahRows = fetch_all_stmt($stmt); } else { $rumahRows = []; $res = $koneksi->query("SELECT * FROM rumah_ibadah WHERE status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'"); while ($row = $res->fetch_assoc()) $rumahRows[] = $row; } foreach ($rumahRows as $row) { $jarak = haversine_meter((float) $lat, (float) $lng, (float) $row['latitude'], (float) $row['longitude']); if ($jarak <= $max_meter) { $row['jarak_meter'] = round($jarak, 2); $rows[] = $row; } } usort($rows, fn($a, $b) => $a['jarak_meter'] <=> $b['jarak_meter']); return $rows[0] ?? null; } function get_penerima_by_id($id) { global $koneksi; $stmt = $koneksi->prepare("SELECT pb.*, ri.nama_rumah_ibadah, ri.user_id AS admin_rumah_user_id, ri.latitude AS ri_latitude, ri.longitude AS ri_longitude FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.id=? LIMIT 1"); $stmt->bind_param('i', $id); $stmt->execute(); return $stmt->get_result()->fetch_assoc(); } function get_rumah_ibadah_by_id($id) { global $koneksi; $stmt = $koneksi->prepare("SELECT ri.*, u.nama AS nama_admin, u.username AS username_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.id=? LIMIT 1"); $stmt->bind_param('i', $id); $stmt->execute(); return $stmt->get_result()->fetch_assoc(); } function can_manage_rumah_ibadah($rumah) { if (!$rumah || !is_logged_in()) return false; if ($_SESSION['role'] === 'super_admin') return true; if ($_SESSION['role'] === 'admin_rumah_ibadah') { return (int)$rumah['user_id'] === (int)$_SESSION['user_id']; } return false; } function can_manage_penerima($penerima) { if (!$penerima || !is_logged_in()) return false; if ($_SESSION['role'] === 'super_admin') return true; if ($_SESSION['role'] === 'penerima' && (int) $penerima['user_id'] === (int) $_SESSION['user_id']) return true; if ($_SESSION['role'] === 'admin_rumah_ibadah') { return !empty($penerima['rumah_ibadah_id']) && is_admin_rumah_ibadah_id((int)$penerima['rumah_ibadah_id'], null, true); } return false; } function insert_riwayat_status($penerima_id, $status_lama, $status_baru, $catatan) { global $koneksi; $uid = (int) $_SESSION['user_id']; $stmt = $koneksi->prepare("INSERT INTO riwayat_status (penerima_id, user_id, status_lama, status_baru, catatan) VALUES (?,?,?,?,?)"); $stmt->bind_param('iisss', $penerima_id, $uid, $status_lama, $status_baru, $catatan); $stmt->execute(); } function get_user_by_username_role($username, $roles) { global $koneksi; if (!is_array($roles)) $roles = [$roles]; $username = clean_text($username, 50); if ($username === '') return null; $placeholders = implode(',', array_fill(0, count($roles), '?')); $types = 's' . str_repeat('s', count($roles)); $params = array_merge([$username], $roles); $stmt = $koneksi->prepare("SELECT id, nama, username, role, status_akun FROM users WHERE username=? AND role IN ($placeholders) AND status_akun='aktif' LIMIT 1"); stmt_bind($stmt, $types, $params); $stmt->execute(); return $stmt->get_result()->fetch_assoc(); } function has_active_penerima_pin($user_id, $exclude_penerima_id = 0) { global $koneksi; $user_id = (int)$user_id; $exclude_penerima_id = (int)$exclude_penerima_id; if ($user_id <= 0) return false; $sql = "SELECT id FROM penerima_bantuan WHERE user_id=? AND status_pengajuan IN ('menunggu','diterima') AND COALESCE(status_hapus,'normal')<>'disetujui'"; $types = 'i'; $params = [$user_id]; if ($exclude_penerima_id > 0) { $sql .= " AND id<>?"; $types .= 'i'; $params[] = $exclude_penerima_id; } $sql .= " LIMIT 1"; $stmt = $koneksi->prepare($sql); stmt_bind($stmt, $types, $params); $stmt->execute(); return (bool)$stmt->get_result()->fetch_assoc(); } function sync_single_penerima_area($penerima_id) { global $koneksi; $penerima_id = (int)$penerima_id; $stmt = $koneksi->prepare("SELECT id, latitude, longitude FROM penerima_bantuan WHERE id=? AND status_pengajuan IN ('menunggu','diterima') AND COALESCE(status_hapus,'normal')<>'disetujui' LIMIT 1"); $stmt->bind_param('i', $penerima_id); $stmt->execute(); $pb = $stmt->get_result()->fetch_assoc(); if (!$pb) return false; $nearest = get_nearest_rumah_ibadah((float)$pb['latitude'], (float)$pb['longitude'], 300); if ($nearest) { $riId = (int)$nearest['id']; $jarak = (float)$nearest['jarak_meter']; $stmtUp = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=?, jarak_meter=? WHERE id=?"); $stmtUp->bind_param('idi', $riId, $jarak, $penerima_id); } else { $stmtUp = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=NULL, jarak_meter=NULL WHERE id=?"); $stmtUp->bind_param('i', $penerima_id); } return $stmtUp->execute(); } function sync_all_penerima_area() { global $koneksi; $ids = []; $res = $koneksi->query("SELECT id FROM penerima_bantuan WHERE status_pengajuan IN ('menunggu','diterima') AND COALESCE(status_hapus,'normal')<>'disetujui'"); while ($row = $res->fetch_assoc()) $ids[] = (int)$row['id']; $updated = 0; foreach ($ids as $id) { if (sync_single_penerima_area($id)) $updated++; } return $updated; } function sync_penerima_area_by_rumah_ibadah($rumah_ibadah_id) { // Tetap menerima parameter lama, tetapi sinkronisasi dihitung ulang secara global agar radius beririsan memilih RI terdekat. return sync_all_penerima_area(); } function detach_penerima_from_rumah_ibadah($rumah_ibadah_id) { global $koneksi; $rumah_ibadah_id = (int) $rumah_ibadah_id; $stmt = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=NULL, jarak_meter=NULL WHERE rumah_ibadah_id=? AND status_pengajuan IN ('menunggu','diterima')"); $stmt->bind_param('i', $rumah_ibadah_id); $stmt->execute(); $affected = $stmt->affected_rows; // Setelah dilepas, cari ulang rumah ibadah aktif terdekat lain jika ada. sync_all_penerima_area(); return $affected; } ?>