180 lines
7.6 KiB
PHP
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); |