first commit
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// Konfigurasi Koneksi Database & Fungsi Utilitas
|
||||
// WebGIS Pontianak — Pengentasan Kemiskinan Berbasis Rumah Ibadah
|
||||
// ============================================================
|
||||
|
||||
// Konfigurasi database
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_NAME', 'webgis_rumah_ibadah');
|
||||
define('DB_USER', 'root');
|
||||
define('DB_PASS', '');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
// Direktori upload foto laporan
|
||||
define('UPLOAD_DIR', __DIR__ . '/uploads/');
|
||||
|
||||
// Mulai session
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ── Koneksi PDO ──────────────────────────────────────────────
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
|
||||
DB_USER,
|
||||
DB_PASS,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'data' => null,
|
||||
'message' => 'Koneksi database gagal: ' . $e->getMessage()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── CORS Headers ─────────────────────────────────────────────
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
// Handle preflight request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FUNGSI UTILITAS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Kirim respon JSON standar lalu hentikan eksekusi.
|
||||
* Format: {"success": true/false, "data": ..., "message": "..."}
|
||||
*/
|
||||
function jsonResponse($data = null, string $message = '', bool $success = true, int $httpCode = 200, array $extra = []): void {
|
||||
http_response_code($httpCode);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$response = [
|
||||
'success' => $success,
|
||||
'data' => $data,
|
||||
'message' => $message
|
||||
];
|
||||
// Gabungkan field tambahan (misal: duplikasi_warning)
|
||||
if (!empty($extra)) {
|
||||
$response = array_merge($response, $extra);
|
||||
}
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bersihkan input string dari tag HTML.
|
||||
*/
|
||||
function clean(string $value): string {
|
||||
return htmlspecialchars(strip_tags(trim($value)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hitung jarak Haversine antara dua koordinat dalam kilometer.
|
||||
*/
|
||||
function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float {
|
||||
$R = 6371; // Radius bumi dalam km
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLng = deg2rad($lng2 - $lng1);
|
||||
$a = sin($dLat / 2) * sin($dLat / 2) +
|
||||
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
|
||||
sin($dLng / 2) * sin($dLng / 2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
||||
return round($R * $c, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-assign: cari rumah ibadah terdekat yang radius-nya mencakup lokasi KK.
|
||||
* Mengembalikan ['rumah_ibadah_id' => int|null, 'jarak_km' => float|null]
|
||||
*/
|
||||
function autoAssign(PDO $pdo, float $lat, float $lng): array {
|
||||
$result = ['rumah_ibadah_id' => null, 'jarak_km' => null];
|
||||
|
||||
$stmt = $pdo->query("SELECT id, lat, lng, radius_km FROM rumah_ibadah");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$bestDist = PHP_FLOAT_MAX;
|
||||
$bestId = null;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$dist = haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
|
||||
// Hanya assign jika KK berada dalam radius RI dan merupakan yang terdekat
|
||||
if ($dist <= (float)$row['radius_km'] && $dist < $bestDist) {
|
||||
$bestDist = $dist;
|
||||
$bestId = (int)$row['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestId !== null) {
|
||||
$result['rumah_ibadah_id'] = $bestId;
|
||||
$result['jarak_km'] = $bestDist;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah user sudah login. Jika belum, kirim 401.
|
||||
* Mengembalikan data session user.
|
||||
*/
|
||||
function requireLogin(): array {
|
||||
if (empty($_SESSION['user'])) {
|
||||
jsonResponse(null, 'Tidak diizinkan. Silakan login terlebih dahulu.', false, 401);
|
||||
}
|
||||
return $_SESSION['user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah role user termasuk dalam daftar yang diizinkan.
|
||||
* Jika tidak, kirim 403.
|
||||
*/
|
||||
function requireRole(array $allowedRoles): array {
|
||||
$user = requireLogin();
|
||||
if (!in_array($user['role'], $allowedRoles)) {
|
||||
jsonResponse(null, 'Akses ditolak. Role Anda tidak memiliki izin.', false, 403);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blokir aksi tulis (POST/PUT/DELETE) untuk role walikota.
|
||||
* Walikota hanya boleh GET.
|
||||
*/
|
||||
function blockWalikotaWrite(): void {
|
||||
if (!empty($_SESSION['user']) && $_SESSION['user']['role'] === 'walikota') {
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
if (in_array($method, ['POST', 'PUT', 'DELETE'])) {
|
||||
jsonResponse(null, 'Walikota hanya memiliki akses baca (read-only).', false, 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate kode pengurus dengan format: RI-[JENIS 3 HURUF]-[RANDOM 4 DIGIT]
|
||||
* Contoh: RI-MSJ-4821, RI-GRJ-7153
|
||||
*/
|
||||
function generateKodePengurus(PDO $pdo, string $jenis): string {
|
||||
// Mapping jenis ke 3 huruf
|
||||
$map = [
|
||||
'Masjid' => 'MSJ',
|
||||
'Gereja' => 'GRJ',
|
||||
'Pura' => 'PRA',
|
||||
'Vihara' => 'VHR',
|
||||
'Klenteng' => 'KLT',
|
||||
'Lainnya' => 'LNY',
|
||||
];
|
||||
$prefix = $map[$jenis] ?? 'LNY';
|
||||
|
||||
// Coba generate kode unik (maks 100 percobaan)
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$angka = str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
$kode = "RI-{$prefix}-{$angka}";
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM rumah_ibadah WHERE kode_pengurus = ? LIMIT 1");
|
||||
$stmt->execute([$kode]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
return $kode;
|
||||
}
|
||||
}
|
||||
// Fallback: tambahkan timestamp
|
||||
return "RI-{$prefix}-" . substr(time(), -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek rate limit untuk IP tertentu. Maks 5 submit per jam.
|
||||
* Mengembalikan true jika masih diizinkan, false jika melebihi batas.
|
||||
*/
|
||||
function checkRateLimit(PDO $pdo, string $ip): bool {
|
||||
// Hapus record yang lebih dari 1 jam
|
||||
$pdo->prepare("DELETE FROM rate_limit WHERE created_at < NOW() - INTERVAL 1 HOUR")->execute();
|
||||
|
||||
// Hitung jumlah submit dalam 1 jam terakhir
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) as cnt FROM rate_limit WHERE ip_address = ? AND created_at >= NOW() - INTERVAL 1 HOUR");
|
||||
$stmt->execute([$ip]);
|
||||
$count = (int)$stmt->fetch()['cnt'];
|
||||
|
||||
if ($count >= 5) {
|
||||
return false; // Sudah melebihi batas
|
||||
}
|
||||
|
||||
// Catat submit baru
|
||||
$stmt = $pdo->prepare("INSERT INTO rate_limit (ip_address) VALUES (?)");
|
||||
$stmt->execute([$ip]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle upload file foto. Mengembalikan path relatif file atau null.
|
||||
*/
|
||||
function handleUpload(string $fieldName): ?string {
|
||||
if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]['error'] !== UPLOAD_ERR_OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $_FILES[$fieldName];
|
||||
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
$maxSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
if (!in_array($file['type'], $allowedTypes)) {
|
||||
jsonResponse(null, 'Tipe file tidak diizinkan. Gunakan JPG, PNG, GIF, atau WebP.', false, 400);
|
||||
}
|
||||
|
||||
if ($file['size'] > $maxSize) {
|
||||
jsonResponse(null, 'Ukuran file melebihi batas 5MB.', false, 400);
|
||||
}
|
||||
|
||||
// Buat direktori upload jika belum ada
|
||||
if (!is_dir(UPLOAD_DIR)) {
|
||||
mkdir(UPLOAD_DIR, 0755, true);
|
||||
}
|
||||
|
||||
// Generate nama file unik
|
||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'laporan_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
|
||||
$dest = UPLOAD_DIR . $filename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $dest)) {
|
||||
jsonResponse(null, 'Gagal menyimpan file foto.', false, 500);
|
||||
}
|
||||
|
||||
return 'uploads/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dapatkan IP address client (mendukung proxy).
|
||||
*/
|
||||
function getClientIP(): string {
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
|
||||
}
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
Reference in New Issue
Block a user