'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);