DEFENSE PREP — CODE REFERENCE
School Tracking System — Code Walkthrough
PHP / MySQL Multi-Role Web Application · Almoataz Adnan · 21F22010
1. Foundation Files (used by every page)
config/[Link] — Database Connection
Creates the PDO connection to MySQL. Three options matter most:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // throw real errors
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // rows as assoc arrays
PDO::ATTR_EMULATE_PREPARES => false, // REAL prepared statements
WHY EMULATE_PREPARES = FALSE MATTERS
This forces PHP to send the query and the user's data to MySQL separately — MySQL itself treats the input strictly as data, never as part
of the SQL command. This is the core SQL-injection defense across the whole app.
includes/[Link] — Session & Access Control
isLoggedIn() — checks $_SESSION['user_id'] is set
requireLogin() — redirects to login if not logged in
requireRole('teacher') — calls requireLogin(), then checks session role matches; this is the role-based access control gate on every
protected page
loginUser() / logoutUser() — write / destroy session data
Also sets date_default_timezone_set('Asia/Muscat') globally.
includes/[Link] — Shared Utilities
sanitize() — strips HTML tags + trims input
hashPassword() — wraps password_hash($password, PASSWORD_DEFAULT) (bcrypt)
isValidFileUpload() — checks upload error code, extension, that the file's real MIME type matches its extension, and size ≤ 10MB
getAttendancePercentage() / getStudentGPA() — SQL aggregate helpers for dashboards
2. Login — [Link]
If already logged in, redirects straight to the role dashboard via a lookup map. On POST, the logic order is important:
1. Look up the user row by username
2. Check lockout first — if failed_attempts >= 5 AND the last attempt was within 15 minutes, block login (admin is exempt from
lockout)
3. If the 15-minute window has passed, reset failed_attempts to 0 automatically
4. Only then check password_verify($password, $user['password'])
if ($user['role'] !== 'admin'
&& (int)$user['failed_attempts'] >= MAX_ATTEMPTS
&& $user['login_timestamp'] !== null) {
// check if still within 15-minute lockout window
}
KEY FACT
Lockout state lives in the database ( users.failed_attempts , users.login_timestamp ) — not the session — so it persists even if the
browser is closed and reopened.
3. Admin Pages ADMIN
FILE WHAT IT DOES
admin/[Link] Dashboard — 6 summary cards (students, teachers, classes, assignments, parents, submissions) + 4 live charts
(attendance trend, attendance breakdown, grade distribution, users by role). Counts pulled with simple SELECT
COUNT(*) queries.
admin/manage_users.php Create / edit / delete users. New passwords run through password_hash() before insert. Editing a user without typing a
new password keeps the old hash untouched.
admin/manage_classes.php Create classes; assign teachers and students via checkboxes — this is what writes rows into the class_teachers and
student_classes junction tables.
admin/[Link] Aggregate reporting views across attendance/grades.
admin/[Link] / Admin-side messaging — send to one role or everyone, view received messages.
[Link]
4. Teacher Pages TEACHER
teacher/[Link] — Fixed Bug #1 (duplicate rows)
Teacher selects a class + date; students are loaded via a join through student_classes . On save, each student's radio choice is written with:
INSERT INTO attendance (student_id, teacher_id, class_id, date, status)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
status = VALUES(status),
teacher_id = VALUES(teacher_id)
WHY THIS FIXES IT
The attendance table has UNIQUE KEY uq_attendance (student_id, class_id, date) — a unique rule across three columns together. If
that exact combination already exists, MySQL doesn't insert a new row — ON DUPLICATE KEY UPDATE tells it to update the existing row's
status instead. This is what stops the same student getting two attendance rows on the same day.
teacher/[Link]
Add / edit / delete grades. Validation before insert:
elseif (!is_numeric($gradeVal) || (float)$gradeVal < 0 || (float)$gradeVal > 100) {
$error = 'Grade value must be a number between 0 and 100.';
}
Delete and edit both filter WHERE teacher_id = ? — a teacher can only touch grades they themselves entered.
teacher/[Link]
Create assignments, optional file attachment, optional deadline. File size limit defined as a named constant: define('MAX_FILE_BYTES', 10 *
1024 * 1024);
teacher/[Link] / [Link]
Send announcements to students/parents in a class; view received messages.
5. Student Pages STUDENT
student/[Link] — Fixed Bug #2 (oversized files not rejected)
On submission, three checks run in order: extension allow-list, real MIME type allow-list, then size:
if (!in_array($ext, $allowed, true) || !in_array($mime, $allowedMimes, true)) {
$error = 'Invalid file type. Allowed: PDF, DOC, DOCX, ZIP.';
} elseif ($fileSize > 10 * 1024 * 1024) {
$error = 'File exceeds 10 MB limit.';
}
WHY THIS FIXES IT
$fileSize comes from PHP as a raw byte count. The fix compares it against 10 * 1024 * 1024 — the real byte value of 10MB
(10,485,760). The earlier broken version compared bytes against a bare 10 , a number that's basically never bigger than a real file's
byte count, so the check never actually triggered.
Duplicate submissions are also blocked at the database level: assignment_submissions has UNIQUE KEY uq_submission (assignment_id,
student_id) . The code catches MySQL's duplicate-key error code directly:
if ($e->getCode() === '23000') {
$error = 'You have already submitted this assignment.';
}
student/view_grades.php / view_attendance.php
Read-only views, always scoped to the logged-in student's own student_id — never accept an ID from the URL for these.
6. Parent Pages PARENT
parent/view_child.php
The parent–child link lives in the parents table ( parent_id , user_id , student_id , relationship ). The page joins parents → students → users
to resolve the child's name and class, then pulls attendance/grade summaries with an optional month filter. If a parent has more than one
child, $_GET['student_id'] switches between them.
7. Database Schema — Junction Tables
TABLE COLUMNS RELATIONSHIP
student_classes student_id, class_id (composite PK) Many-to-many: students ↔ classes
class_teachers class_id, teacher_id (composite PK) Many-to-many: classes ↔ teachers
Both use ON DELETE CASCADE — deleting a class automatically removes its links in these tables.
COMMON SLIP TO AVOID
The second junction table is called class_teachers, not "teacher_classes." Say the exact name.
8. Quick-Fire Facts
TOPIC ANSWER
Password hashing password_hash() / password_verify() — bcrypt via PASSWORD_DEFAULT
SQL injection defense PDO prepared statements, EMULATE_PREPARES => false
Account lockout rule 5 failed attempts → 15-minute lockout (admin exempt), stored in users table
File upload limit 10MB, checked via real byte comparison (10 × 1024 × 1024)
Attendance statuses present , absent , late — ENUM column
Grade range 0–100, stored as DECIMAL(5,2) (e.g. 87.50)
Attendance unique key (student_id, class_id, date) — three columns together
Submission unique key (assignment_id, student_id) — blocks double submission
School Tracking System Web Application · Code reference compiled for final-year defense preparation