Jump to content

Smart Attendance and Monitoring


Recommended Posts

I want to design an attendance system such that the class teacher can scan students' ID card QR code using their phone, and it will be recorded immediately in the database.

Currently, I have tested HTML QR code and Quagga, but I haven't tested scanning the ID card yet. I am only testing with a phone and PC camera because I want the user to be able to use either device to scan the student ID card.

I'm not sure where the error is. Could anyone please enlighten me?

Here is the dashboard.php where the user can click scan ID Card

<?php
// pages/dashboard.php
session_start();
if (!isset($_SESSION['username'])) {
    header('Location: login.php');
    exit();
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../css/styles.css">
    <title>Dashboard</title>
</head>
<body>
    <div class="container">
        <header>
            <div id="branding">
                <h1>QR Smart Attendance</h1>
            </div>
            <nav>
                <ul>
                    <li><a href="dashboard.php">Dashboard</a></li>
                    <li><a href="generate_id_card.php">Generate ID Card</a></li>
                    <li><a href="record_attendance.php">Record Attendance</a></li>
                    <li><a href="transfer_attendance.php">Transfer Attendance</a></li>
                    <li><a href="logout.php">Logout</a></li>
                </ul>
            </nav>
        </header>
        <div class="dashboard-container">
            <h2>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?></h2>
            
            <!-- Add a button for scanning ID cards -->
            <button id="scanButton">Scan ID Card</button>

            <!-- Container for the scanner -->
            <div id="scanner-container" style="width: 100%; height: 400px; display:none;"></div>

            <script src="https://unpkg.com/@ericblade/quagga2/dist/quagga.min.js"></script>
            <script>
                document.getElementById('scanButton').addEventListener('click', function() {
                    // Display the scanner container
                    document.getElementById('scanner-container').style.display = 'block';

                    Quagga.init({
                        inputStream: {
                            name: "Live",
                            type: "LiveStream",
                            target: document.querySelector('#scanner-container'),
                            constraints: {
                                width: 640,
                                height: 480,
                                facingMode: "environment" // or "user" for front-facing camera
                            }
                        },
                        decoder: {
                            readers: ["qr_reader"] // Use the QR reader
                        },
                        locate: true // Enable locating the QR code in the image
                    }, function(err) {
                        if (err) {
                            console.error('QuaggaJS initialization failed:', err);
                            return;
                        }
                        console.log("QuaggaJS initialization finished. Ready to start");
                        Quagga.start();
                    });

                    // Handle scanned results
                    Quagga.onDetected(function(result) {
                        if (result && result.codeResult && result.codeResult.code) {
                            console.log('Scanned code:', result.codeResult.code);
                            // Redirect to record_attendance.php with student_id or handle accordingly
                            window.location.href = 'record_attendance.php?student_id=' + result.codeResult.code;
                        } else {
                            console.warn('No code detected');
                        }
                    });

                    // Optional: Handle processing errors
                    Quagga.onProcessed(function(result) {
                        var drawingCtx = Quagga.canvas.ctx.overlay,
                            drawingCanvas = Quagga.canvas.dom.overlay;

                        if (result) {
                            if (result.boxes) {
                                drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
                                result.boxes.filter(function(box) {
                                    return box !== result.box;
                                }).forEach(function(box) {
                                    Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, {
                                        color: "green",
                                        lineWidth: 2
                                    });
                                });
                            }

                            if (result.box) {
                                Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, {
                                    color: "#00F",
                                    lineWidth: 2
                                });
                            }

                            if (result.codeResult && result.codeResult.code) {
                                Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, {
                                    color: 'red',
                                    lineWidth: 3
                                });
                            }
                        }
                    });
                });
            </script>
        </div>
    </div>
</body>
</html>

And here is record_attendance.php

<?php
// pages/record_attendance.php
session_start();
if (!isset($_SESSION['username'])) {
    header('Location: login.php');
    exit();
}

require_once '../includes/db.php'; // Include db.php for database connection and functions

// Check if a student ID is scanned and process attendance
if (isset($_GET['student_id'])) {
    $student_id = sanitizeInput($_GET['student_id']);
    $date = date('Y-m-d');

    // Check if attendance record already exists for today
    $query = "SELECT * FROM attendance WHERE student_id = ? AND date = ?";
    $stmt = $conn->prepare($query);
    $stmt->bind_param("is", $student_id, $date);
    $stmt->execute();
    $stmt->store_result();

    if ($stmt->num_rows == 0) {
        // If no record exists, insert new attendance record
        $query = "INSERT INTO attendance (student_id, date, status) VALUES (?, ?, 'present')";
        $stmt = $conn->prepare($query);
        $stmt->bind_param("is", $student_id, $date);
        $stmt->execute();

        // Optional: Redirect back to dashboard after recording attendance
        header('Location: dashboard.php');
        exit();
    } else {
        // Optional: Handle case where attendance is already recorded for today
        echo "Attendance already recorded for today.";
        // Redirect or display message as needed
    }
} else {
    // Handle case where no student ID is scanned
    echo "No student ID received. Please scan a valid student ID.";
    // Redirect or display message as needed
}
?>

Here is generate_id_card.php

<?php
// pages/generate_id_card.php
session_start();
if (!isset($_SESSION['username']) || $_SESSION['role'] != 'admin') {
    header('Location: login.php');
    exit();
}

require_once '../includes/db.php'; // Ensure this path is correct
require_once '../includes/functions.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $name = sanitizeInput($_POST['name']);
    $class = sanitizeInput($_POST['class']);
    $photo = $_FILES['photo'];

    if ($photo['error'] == 0) {
        $photo_path = '../images/' . basename($photo['name']);
        move_uploaded_file($photo['tmp_name'], $photo_path);

        // Insert student data into the database
        $insert_query = "INSERT INTO students (name, class, photo) VALUES (?, ?, ?)";
        $stmt = $conn->prepare($insert_query);
        $stmt->bind_param("sss", $name, $class, $photo_path);
        $stmt->execute();

        // Retrieve the inserted student's ID
        $student_id = $conn->insert_id;

        // Generate ID card with the student's details
        $id_card = generateIDCard($name, $class, $photo_path);

        echo "<p>ID Card generated: <a href='$id_card'>Download ID Card</a></p>";
    } else {
        echo "<p>Error uploading photo.</p>";
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../css/styles.css">
    <title>Generate ID Card</title>
</head>
<body>
    <div class="container">
        <header>
            <div id="branding">
                <h1>QR Smart Attendance</h1>
            </div>
        </header>
        <div class="generate-container">
            <h2>Generate Student ID Card</h2>
            <form method="post" enctype="multipart/form-data">
                <input type="text" name="name" placeholder="Student Name" required>
                <input type="text" name="class" placeholder="Class" required>
                <input type="file" name="photo" accept="image/*" required>
                <button type="submit">Generate</button>
            </form>
        </div>
    </div>
</body>
</html>

Here is functions.php

<?php
require_once '../vendor/autoload.php';

use Endroid\QrCode\QrCode;
use Endroid\QrCode\Writer\PngWriter;

function generateIDCard($name, $class, $photo_path) {
    // Ensure QR code directory exists
    $qrCodeDir = '../images/qrcodes';
    if (!is_dir($qrCodeDir)) {
        mkdir($qrCodeDir, 0755, true);
    }

    // Generate a unique ID for the QR code
    $uniqueId = uniqid();

    // Create the QR code
    $qrCode = new QrCode($uniqueId);
    $qrCode->setSize(300);

    // Set the QR code file path
    $qrCodePath = "$qrCodeDir/$uniqueId.png";
    $qrCode->writeFile($qrCodePath);

    // Ensure font file exists
    $fontPath = '../fonts/arial.ttf';
    if (!file_exists($fontPath)) {
        die('Font file not found.');
    }

    // Create the ID card image
    $idCard = imagecreate(600, 400);
    $white = imagecolorallocate($idCard, 255, 255, 255);
    $black = imagecolorallocate($idCard, 0, 0, 0);

    // Add student details
    imagettftext($idCard, 20, 0, 20, 50, $black, $fontPath, "Name: $name");
    imagettftext($idCard, 20, 0, 20, 100, $black, $fontPath, "Class: $class");
    imagettftext($idCard, 20, 0, 20, 150, $black, $fontPath, "ID: $uniqueId");

    // Add the student photo
    $photo = imagecreatefromjpeg($photo_path);
    if ($photo) {
        imagecopyresized($idCard, $photo, 400, 20, 0, 0, 160, 160, imagesx($photo), imagesy($photo));
    } else {
        die('Photo file not found.');
    }

    // Add the QR code
    $qrImage = imagecreatefrompng($qrCodePath);
    if ($qrImage) {
        imagecopyresized($idCard, $qrImage, 20, 200, 0, 0, 160, 160, imagesx($qrImage), imagesy($qrImage));
    } else {
        die('QR code file not found.');
    }

    // Ensure ID card directory exists
    $idCardDir = '../images/idcards';
    if (!is_dir($idCardDir)) {
        mkdir($idCardDir, 0755, true);
    }

    // Save the ID card image
    $idCardPath = "$idCardDir/$uniqueId.png";
    imagepng($idCard, $idCardPath);

    // Free up memory
    imagedestroy($idCard);
    imagedestroy($photo);
    imagedestroy($qrImage);

    return $idCardPath;
}
?>

My database


DROP TABLE IF EXISTS `attendance`;
CREATE TABLE IF NOT EXISTS `attendance` (
  `id` int NOT NULL AUTO_INCREMENT,
  `student_id` int NOT NULL,
  `date` date NOT NULL,
  `status` enum('present','absent') NOT NULL,
  PRIMARY KEY (`id`),
  KEY `student_id` (`student_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;



DROP TABLE IF EXISTS `notifications`;
CREATE TABLE IF NOT EXISTS `notifications` (
  `id` int NOT NULL AUTO_INCREMENT,
  `message` text NOT NULL,
  `date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- --------------------------------------------------------

--
-- Table structure for table `students`
--

DROP TABLE IF EXISTS `students`;
CREATE TABLE IF NOT EXISTS `students` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `class` varchar(50) NOT NULL,
  `photo` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `role` enum('admin','teacher') NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

Link to comment
Share on other sites

5 hours ago, Olumide said:

I'm not sure where the error is

you haven't stated what the code does or does not do.

the user interface (UI)/user experience (UX), after clicking on the Scan ID card button, shouldn't require the user to be navigating back and forth between pages. the user should be able to continue to scan id cards, with any result message being displayed on the current user interface. to do this, you would use ajax to submit the data, then display any response. you should also be using a post method request to submit the scanned student_id.

5 hours ago, Olumide said:
sanitizeInput

whatever this function is doing, it is probably incorrect. you should only trim input data, mainly so that you can detect if it was all white-space characters, then validate the trimmed value. since this code requires a student_id input, at a minimum, you would validate that the trimmed value is not an empty string, before using it.

the attendance table student_id and date columns should be defined as a composite unique index. you would then simply just attempt to insert the data. if the combination of student_id and date doesn't exist, the row will be inserted. you would setup and display a message stating that the data was inserted. if a row of matching data already exists, the query will throw an exception (you should be using exceptions for database statement errors, this is the default setting now in php8+.) in the exception catch code, you would test if the error number is for a duplicate index error. if it is, setup and display a message letting the user know that the data has already been inserted. if the error number is for anything else, which would include things like mistakes in the sql query statement, just rethrow the exception and let php handle it.

i do see a possible problem, which would be producing a database error in the current code. you are not fetching the data from the SELECT query, so the INSERT query should be failing with an error (i don't know if the store_result() call prevents this), but since you will change your code to just use the INSERT query as described in the above paragraph, this potential problem will go away.

your user login should store the user_id in the session variable, then query on each page request to get any other user data, such as the username, user permission. this insures that any changes made to the user data will take effect on the very next page request, without requiring the user to log out and back in again.

the file upload handling in generate_id_card.php is not testing if there is any $_POST/$_FILES data before trying to use the form data. if the total size of the form data exceeds the post_max_size setting, both $_POST and $_FILES will be empty. after you test that a post method form has been submitted, you must test that there is data in $_POST/$_FILES before using it.

also in generate_id_card.php (which is actually a student registration operation), you should be selecting from existing classes. you would submit the class_id, not the class name. you should have first name and last name columns, so that you can distinguish between names like Martin Ross and Ross Martin. if you store the uploaded file using the generated student_id/insert_id, as part of the filename, you will avoid the need to check for duplicate filenames, because student_id are unique. any time your code knows the student_id, you can directly find the uploaded photo using that id.

Edited by mac_gyver
Link to comment
Share on other sites

2 hours ago, mac_gyver said:

you haven't stated what the code does or does not do.

the user interface (UI)/user experience (UX), after clicking on the Scan ID card button, shouldn't require the user to be navigating back and forth between pages. the user should be able to continue to scan id cards, with any result message being displayed on the current user interface. to do this, you would use ajax to submit the data, then display any response. you should also be using a post method request to submit the scanned student_id.

whatever this function is doing, it is probably incorrect. you should only trim input data, mainly so that you can detect if it was all white-space characters, then validate the trimmed value. since this code requires a student_id input, at a minimum, you would validate that the trimmed value is not an empty string, before using it.

the attendance table student_id and date columns should be defined as a composite unique index. you would then simply just attempt to insert the data. if the combination of student_id and date doesn't exist, the row will be inserted. you would setup and display a message stating that the data was inserted. if a row of matching data already exists, the query will throw an exception (you should be using exceptions for database statement errors, this is the default setting now in php8+.) in the exception catch code, you would test if the error number is for a duplicate index error. if it is, setup and display a message letting the user know that the data has already been inserted. if the error number is for anything else, which would include things like mistakes in the sql query statement, just rethrow the exception and let php handle it.

i do see a possible problem, which would be producing a database error in the current code. you are not fetching the data from the SELECT query, so the INSERT query should be failing with an error (i don't know if the store_result() call prevents this), but since you will change your code to just use the INSERT query as described in the above paragraph, this potential problem will go away.

your user login should store the user_id in the session variable, then query on each page request to get any other user data, such as the username, user permission. this insures that any changes made to the user data will take effect on the very next page request, without requiring the user to log out and back in again.

the file upload handling in generate_id_card.php is not testing if there is any $_POST/$_FILES data before trying to use the form data. if the total size of the form data exceeds the post_max_size setting, both $_POST and $_FILES will be empty. after you test that a post method form has been submitted, you must test that there is data in $_POST/$_FILES before using it.

also in generate_id_card.php (which is actually a student registration operation), you should be selecting from existing classes. you would submit the class_id, not the class name. you should have first name and last name columns, so that you can distinguish between names like Martin Ross and Ross Martin. if you store the uploaded file using the generated student_id/insert_id, as part of the filename, you will avoid the need to check for duplicate filenames, because student_id are unique. any time your code knows the student_id, you can directly find the uploaded photo using that id.

 

 

Thank you so much for the support and all the guidance. Though, I have solved the problem by using instascan instead of the previous quagga and html5 qrcode. But getting worked is nothing but to take to practices all the programming advices you have highlighted which is very essential.

I cannot but to always thank you all in this forum for your help and I can say ever since I have joined this forum, I have never for once regret that. You are all amazing.

Cheers 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.