"error", "message" => "Method tidak diizinkan"]); exit; } // Validasi input foto if (!isset($_POST['foto'])) { echo json_encode(["status" => "error", "message" => "Data foto tidak ada"]); exit; } $fotoData = $_POST['foto']; $id_occupant = null; $token = null; // Method 1: Token-based (from QR code scan) if (isset($_POST['token']) && trim($_POST['token']) !== '') { $token = trim($_POST['token']); // Validate token $stmt = $pdo->prepare("SELECT ft.id, ft.id_occupant, ft.status FROM foto_tokens ft WHERE ft.token = ?"); $stmt->execute([$token]); $tokenRow = $stmt->fetch(); if (!$tokenRow) { echo json_encode(["status" => "error", "message" => "Token tidak valid"]); exit; } if ($tokenRow['status'] === 'used') { echo json_encode(["status" => "error", "message" => "Token sudah digunakan (kadaluwarsa)"]); exit; } $id_occupant = $tokenRow['id_occupant']; } // Method 2: Direct id_occupant (backward compat) elseif (isset($_POST['id_occupant']) && trim($_POST['id_occupant']) !== '') { $id_occupant = trim($_POST['id_occupant']); } else { echo json_encode(["status" => "error", "message" => "Token atau ID Occupant diperlukan"]); exit; } // Validasi id_occupant ada di database $cek = $pdo->prepare("SELECT id_occupant, foto FROM occupant WHERE id_occupant = ?"); $cek->execute([$id_occupant]); $occupant = $cek->fetch(); if (!$occupant) { echo json_encode(["status" => "error", "message" => "Occupant tidak ditemukan"]); exit; } // Decode base64 image if (preg_match('/^data:image\/(png|jpeg|jpg|webp);base64,/', $fotoData, $matches)) { $ext = $matches[1] === 'jpeg' ? 'jpg' : $matches[1]; $fotoData = preg_replace('/^data:image\/\w+;base64,/', '', $fotoData); $fotoData = base64_decode($fotoData); if ($fotoData === false) { echo json_encode(["status" => "error", "message" => "Gagal decode gambar"]); exit; } // Validate image size (min 50KB, max 10MB) $imgSize = strlen($fotoData); if ($imgSize < 50000) { echo json_encode(["status" => "error", "message" => "Ukuran gambar terlalu kecil (min 50KB)"]); exit; } if ($imgSize > 10485760) { echo json_encode(["status" => "error", "message" => "Ukuran gambar terlalu besar (max 10MB)"]); exit; } } else { echo json_encode(["status" => "error", "message" => "Format gambar tidak valid"]); exit; } // Buat folder jika belum ada $uploadDir = __DIR__ . '/images/foto/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } // Hapus foto lama jika ada if (!empty($occupant['foto'])) { $oldPath = __DIR__ . '/' . $occupant['foto']; if (file_exists($oldPath)) { unlink($oldPath); } } // Simpan foto baru $fotoName = 'foto_' . $id_occupant . '_' . time() . '.' . $ext; $targetPath = $uploadDir . $fotoName; if (file_put_contents($targetPath, $fotoData) === false) { echo json_encode(["status" => "error", "message" => "Gagal menyimpan foto"]); exit; } // Save capture metadata for audit trail $metadata = [ 'liveness_score' => isset($_POST['liveness_score']) ? intval($_POST['liveness_score']) : null, 'capture_time' => isset($_POST['capture_time']) ? $_POST['capture_time'] : null, 'device_info' => isset($_POST['device_info']) ? substr($_POST['device_info'], 0, 500) : null, 'screen_res' => isset($_POST['screen_res']) ? $_POST['screen_res'] : null, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null, 'saved_at' => date('Y-m-d H:i:s'), ]; $metaPath = $uploadDir . 'meta_' . $id_occupant . '_' . time() . '.json'; file_put_contents($metaPath, json_encode($metadata, JSON_PRETTY_PRINT)); $fotoRelPath = 'images/foto/' . $fotoName; // Update kolom foto di tabel occupant + mark token as used try { $pdo->beginTransaction(); $stmt = $pdo->prepare("UPDATE occupant SET foto = ? WHERE id_occupant = ?"); $stmt->execute([$fotoRelPath, $id_occupant]); // Mark token as used if token-based if ($token) { $pdo->prepare("UPDATE foto_tokens SET status = 'used', used_at = NOW() WHERE token = ?") ->execute([$token]); } $pdo->commit(); // Update state untuk ESP32 (memberi sinyal polling) $stateFile = __DIR__ . '/esp32_state.json'; if (file_exists($stateFile)) { $stateData = json_decode(file_get_contents($stateFile), true); if (is_array($stateData)) { $stateData['photo_done'] = true; file_put_contents($stateFile, json_encode($stateData)); } } echo json_encode([ "status" => "success", "message" => "Foto berhasil disimpan", "foto" => $fotoRelPath ]); } catch (PDOException $e) { $pdo->rollBack(); if (file_exists($targetPath)) { unlink($targetPath); } echo json_encode([ "status" => "error", "message" => "Gagal update database: " . $e->getMessage() ]); } ?>