35132-vm/api/attendance_handler.php
Flatlogic Bot fd73082fc6 v3
2025-10-23 09:12:51 +00:00

180 lines
7.6 KiB
PHP

<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
// --- Helper function to calculate distance between two geo-coordinates ---
function haversine_distance($lat1, $lon1, $lat2, $lon2) {
$earth_radius = 6371000; // meters
$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 $earth_radius * $c;
}
$response = ['status' => 'error', 'message' => 'Invalid request.'];
$action = $_GET['action'] ?? null;
try {
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
switch ($action) {
case 'get_courses':
$stmt = $pdo->query("SELECT id, name FROM courses ORDER BY name");
$courses = $stmt->fetchAll();
$response = ['status' => 'success', 'courses' => $courses];
break;
case 'get_attendance_history':
// Assuming a logged-in student with ID 1 for demonstration
$student_id = 1;
$stmt = $pdo->prepare("
SELECT c.name AS course_name, ar.check_in_time, ar.status, ar.selfie_path
FROM attendance_records ar
JOIN courses c ON ar.course_id = c.id
WHERE ar.student_id = :student_id
ORDER BY ar.check_in_time DESC
");
$stmt->execute(['student_id' => $student_id]);
$history = $stmt->fetchAll();
$response = ['status' => 'success', 'history' => $history];
break;
case 'get_faculty_attendance':
$course_id = filter_input(INPUT_GET, 'course_id', FILTER_VALIDATE_INT);
if (!$course_id) {
$response['message'] = 'Invalid course ID.';
break;
}
$stmt = $pdo->prepare("
SELECT ar.student_id, ar.check_in_time, ar.status, ar.selfie_path, ar.latitude, ar.longitude
FROM attendance_records ar
WHERE ar.course_id = :course_id
ORDER BY ar.check_in_time DESC
");
$stmt->execute(['course_id' => $course_id]);
$records = $stmt->fetchAll();
$response = ['status' => 'success', 'records' => $records];
break;
case 'get_locations_for_course':
$course_id = filter_input(INPUT_GET, 'course_id', FILTER_VALIDATE_INT);
if (!$course_id) {
$response['message'] = 'Invalid course ID.';
break;
}
$stmt = $pdo->prepare("
SELECT l.id, l.name, l.latitude, l.longitude, l.radius
FROM locations l
JOIN course_locations cl ON l.id = cl.location_id
WHERE cl.course_id = :course_id
ORDER BY l.name
");
$stmt->execute(['course_id' => $course_id]);
$locations = $stmt->fetchAll();
$response = ['status' => 'success', 'locations' => $locations];
break;
default:
$response['message'] = 'Invalid GET action.';
break;
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'check_in') {
// --- Input validation ---
$student_id = filter_input(INPUT_POST, 'student_id', FILTER_VALIDATE_INT);
$course_id = filter_input(INPUT_POST, 'course_id', FILTER_VALIDATE_INT);
$latitude = filter_input(INPUT_POST, 'latitude', FILTER_VALIDATE_FLOAT);
$longitude = filter_input(INPUT_POST, 'longitude', FILTER_VALIDATE_FLOAT);
$location_id = filter_input(INPUT_POST, 'location_id', FILTER_VALIDATE_INT); // From QR code
if (!$student_id || !$course_id || $latitude === false || $longitude === false || !isset($_FILES['selfie'])) {
$response['message'] = 'Missing or invalid parameters.';
echo json_encode($response);
exit;
}
// --- 1. Fetch valid locations for the course ---
$sql = "
SELECT l.id, l.latitude, l.longitude, l.radius
FROM locations l
JOIN course_locations cl ON l.id = cl.location_id
WHERE cl.course_id = :course_id
";
$params = ['course_id' => $course_id];
if ($location_id) {
$sql .= " AND l.id = :location_id";
$params['location_id'] = $location_id;
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$valid_locations = $stmt->fetchAll();
if (empty($valid_locations)) {
$response['message'] = $location_id
? 'The scanned QR code is for a location not associated with this course.'
: 'No valid check-in locations found for this course.';
echo json_encode($response);
exit;
}
// --- 2. Check if student is within any valid location (Geofence) ---
$is_in_location = false;
foreach ($valid_locations as $location) {
$distance = haversine_distance($latitude, $longitude, $location['latitude'], $location['longitude']);
if ($distance <= $location['radius']) {
$is_in_location = true;
break;
}
}
if (!$is_in_location) {
$response['status'] = 'failed_location';
$response['message'] = 'Check-in failed. You are not within a valid location for this course.';
$log_stmt = $pdo->prepare(
"INSERT INTO attendance_records (student_id, course_id, latitude, longitude, selfie_path, status) VALUES (:student_id, :course_id, :latitude, :longitude, '', 'failed_location')"
);
$log_stmt->execute(compact('student_id', 'course_id', 'latitude', 'longitude'));
echo json_encode($response);
exit;
}
// --- 3. Handle the selfie upload ---
$upload_dir = __DIR__ . '/../assets/images/attendance_selfies/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0775, true);
}
$file_ext = pathinfo($_FILES['selfie']['name'], PATHINFO_EXTENSION);
$file_name = uniqid() . '_' . time() . '.' . $file_ext;
$selfie_path = $upload_dir . $file_name;
$selfie_db_path = 'assets/images/attendance_selfies/' . $file_name;
if (move_uploaded_file($_FILES['selfie']['tmp_name'], $selfie_path)) {
// --- 4. Log the successful attendance record ---
$stmt = $pdo->prepare(
"INSERT INTO attendance_records (student_id, course_id, latitude, longitude, selfie_path, status) VALUES (:student_id, :course_id, :latitude, :longitude, :selfie_path, 'success')"
);
$stmt->execute([
'student_id' => $student_id,
'course_id' => $course_id,
'latitude' => $latitude,
'longitude' => $longitude,
'selfie_path' => $selfie_db_path
]);
$response['status'] = 'success';
$response['message'] = 'Check-in successful!';
} else {
$response['status'] = 'failed_selfie';
$response['message'] = 'Failed to save selfie image.';
}
}
} catch (PDOException $e) {
// In production, log this error instead of echoing
$response['message'] = 'Database error: ' . $e->getMessage();
}
echo json_encode($response);