Öğrenci Ödev Takip Sistemi — Proje Rehberi
Bu rehber, projedeki her dosya ve klasörü öğretici bir biçimde açıklar. Amaç sadece dosyaların ne işe yaradığını söylemek değil; aynı zamanda neden o şekilde yazıldığını ve hangi kod mantığıyla çalıştığını göstermektir.
PHP, MySQL, Bootstrap ve temel web güvenliği konularını öğrenmek için uygun bir başlangıçtır.
Klasör Yapısı
OdevSistem/
├── config/
│ └── db.php
├── includes/
│ ├── auth.php
│ ├── header.php
│ └── footer.php
├── teacher/
│ ├── dashboard.php
│ ├── assignments.php
│ ├── add_assignment.php
│ ├── edit_assignment.php
│ ├── view_assignment.php
│ ├── delete_assignment.php
│ ├── users.php
│ ├── add_user.php
│ ├── edit_user.php
│ └── delete_user.php
├── student/
│ ├── dashboard.php
│ ├── assignments.php
│ ├── view_assignment.php
│ ├── submit_assignment.php
│ └── my_submissions.php
├── uploads/
│ └── .htaccess
├── index.php
├── login.php
├── logout.php
└── odev_sistemi.sql
Bu yapı neden önemlidir?
- config/ ayar dosyalarını tutar.
- includes/ tekrar kullanılan ortak kodları tutar.
- teacher/ öğretmen işlemlerini ayırır.
- student/ öğrenci işlemlerini ayırır.
- uploads/ yüklenen dosyaları saklar.
Bu ayrım, kodun daha düzenli olmasını sağlar. Her şey tek dosyada olsaydı proje kısa sürede çöp olurdu.
config/
config/db.php — Veritabanı Bağlantısı
Bu dosya projenin temelidir. Veritabanı bağlantısı burada kurulur ve genelde her PHP dosyasında en başta dahil edilir.
Temel görevleri:
- session başlatır
- veritabanına bağlanır
- ortak sabitleri tanımlar
Örnek bağlantı kodu:
<?php
session_start();
$host = 'localhost';
$dbname = 'odev_sistemi';
$user = 'root';
$pass = '';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Veritabanı bağlantı hatası: " . $e->getMessage());
}
?>
Neden PDO kullanılır?
- SQL injection saldırılarına karşı daha güvenlidir
- prepared statement destekler
- hata yönetimi daha temizdir
- modern ve daha profesyonel bir yöntemdir
Örnek güvenli sorgu:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
Buradaki mantık şu: kullanıcıdan gelen veri sorguya direkt gömülmez. Önce sorgu hazırlanır, sonra veri ayrı şekilde gönderilir. Bu da saldırı riskini azaltır.
includes/
auth.php — Yetki ve Oturum Kontrolü
Bu dosya kullanıcı girişini ve rol kontrolünü yönetir. Yani biri öğretmen sayfasına girmeye çalışıyorsa burada durdurulur.
Örnek yardımcı fonksiyonlar:
<?php
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isTeacher() {
return isset($_SESSION['role']) && $_SESSION['role'] === 'ogretmen';
}
function isStudent() {
return isset($_SESSION['role']) && $_SESSION['role'] === 'ogrenci';
}
function requireLogin() {
if (!isLoggedIn()) {
header("Location: login.php");
exit;
}
}
function requireTeacher() {
if (!isTeacher()) {
header("Location: ../student/dashboard.php");
exit;
}
}
function requireStudent() {
if (!isStudent()) {
header("Location: ../teacher/dashboard.php");
exit;
}
}
function h($value) {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
?>
h() neden önemlidir?
Kullanıcıdan veri alıp doğrudan ekrana basarsan XSS açığı oluşabilir.
Kötü örnek:
echo $_POST['ad'];
Doğru örnek:
echo h($_POST['ad']);
Eğer kullanıcı şunu yazarsa:
<script>alert('hack')</script>
sen bunu filtrelemezsen tarayıcı çalıştırır. Bu bayağı rezil bir açık olur.
header.php ve footer.php
Bu dosyalar tekrar eden sayfa iskeletini yönetir.
header.php örneği:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Ödev Sistemi</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
</head>
<body>
<main class="container mt-4">
footer.php örneği:
</main>
</body>
</html>
Bunun faydası şu: her dosyada aynı HTML'i tekrar tekrar yazmazsın.
Login Sistemi
Giriş sistemi, projenin en kritik yerlerinden biridir. Burada hata yaparsan tüm sistemin güvenliği çöker.
Temel mantık:
- kullanıcı adı alınır
- veritabanında aranır
- şifre hash ile doğrulanır
- başarılıysa session oluşturulur
Örnek login kodu:
<?php
require_once 'config/db.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$password = trim($_POST['password']);
$stmt = $pdo->prepare("SELECT id, full_name, password, role FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['full_name'] = $user['full_name'];
$_SESSION['role'] = $user['role'];
if ($user['role'] === 'ogretmen') {
header("Location: teacher/dashboard.php");
} else {
header("Location: student/dashboard.php");
}
exit;
} else {
$error = "Kullanıcı adı veya şifre hatalı.";
}
}
?>
Neden password_verify kullanılır?
Şifreler düz metin olarak tutulmaz. Veritabanına hashlenmiş hali kaydedilir.
Şifre oluşturma örneği:
$hash = password_hash($password, PASSWORD_DEFAULT);
Güvenlik detayları:
- prepared statement kullanılır
- password_verify ile hash kontrol edilir
- session_regenerate_id ile session fixation azaltılır
- gerekirse başarısız girişlerde bekletme eklenebilir
Teacher Panel
Öğretmen paneli, ödevleri ve kullanıcıları yöneten alandır.
Gösterilen bilgiler:
- öğrenci sayısı
- toplam ödev sayısı
- teslim sayısı
Basit istatistik sorguları:
SELECT COUNT(*) FROM users WHERE role = 'ogrenci';
SELECT COUNT(*) FROM assignments;
SELECT COUNT(*) FROM submissions;
JOIN neden kullanılır?
Birden fazla tabloyu ilişkilendirip tek sorguda bilgi çekmek için.
Örnek JOIN:
SELECT a.title, a.due_date, u.full_name AS teacher_name
FROM assignments a
JOIN users u ON a.created_by = u.id;
Bu sorgu, ödevi kimin oluşturduğunu gösterebilir. Ayrı ayrı sorgu yazmak yerine tek sorguda iş bitirirsin.
Örnek öğretmen ödev listeleme kodu:
$stmt = $pdo->query("
SELECT a.id, a.title, a.due_date, u.full_name AS teacher_name
FROM assignments a
JOIN users u ON a.created_by = u.id
ORDER BY a.due_date ASC
");
$assignments = $stmt->fetchAll();
Student Panel
Öğrenci paneli, öğrencinin kendisine ait ödevleri görmesini ve teslim yapmasını sağlar.
İçerik:
- ödev listesi
- teslim durumu
- dosya yükleme
Durumlar:
- teslim edildi
- teslim edilmedi
- süresi doldu
Öğrenciye özel teslim durumunu çekme:
SELECT a.id, a.title, a.due_date,
s.id AS sub_id,
s.submitted_at
FROM assignments a
LEFT JOIN submissions s
ON s.assignment_id = a.id AND s.student_id = ?
Buradaki kritik kısım AND s.student_id = ?. Bu yoksa diğer öğrencilerin teslim bilgileri birbirine girer. O zaman sistem saçmalar.
Dosya Yükleme Güvenliği
Dosya yükleme kısmı en riskli bölümlerden biridir. Bunu gevşek yazarsan sisteme zararlı dosya atılabilir.
Kontroller:
- uzantı kontrolü
- MIME kontrolü
- maksimum dosya boyutu
- benzersiz dosya adı üretme
Örnek yükleme kontrolü:
$allowedExt = ['pdf', 'doc', 'docx', 'zip', 'rar', 'txt', 'png', 'jpg', 'jpeg'];
$maxSize = 10 * 1024 * 1024;
$fileName = $_FILES['file']['name'];
$fileTmp = $_FILES['file']['tmp_name'];
$fileSize = $_FILES['file']['size'];
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt)) {
die("Geçersiz dosya uzantısı.");
}
if ($fileSize > $maxSize) {
die("Dosya boyutu çok büyük.");
}
$newName = uniqid() . "_" . basename($fileName);
move_uploaded_file($fileTmp, "uploads/" . $newName);
Daha sağlam dosya adı üretme:
$newName = sprintf('%d_%d_%s.%s', $assignmentId, $studentId, bin2hex(random_bytes(8)), $ext);
Bu yöntem aynı isim çakışmalarını azaltır ve biraz daha kontrollüdür.
Veritabanı
Veritabanı tarafında üç temel tablo vardır:
- users
- assignments
- submissions
users tablosu örneği:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(100) NOT NULL,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('ogretmen', 'ogrenci') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
assignments tablosu örneği:
CREATE TABLE assignments (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT,
due_date DATE NOT NULL,
created_by INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id)
);
submissions tablosu örneği:
CREATE TABLE submissions (
id INT AUTO_INCREMENT PRIMARY KEY,
assignment_id INT NOT NULL,
student_id INT NOT NULL,
file_name VARCHAR(255) NOT NULL,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_submission (assignment_id, student_id),
FOREIGN KEY (assignment_id) REFERENCES assignments(id) ON DELETE CASCADE,
FOREIGN KEY (student_id) REFERENCES users(id) ON DELETE CASCADE
);
Neden UNIQUE?
Bir öğrenci aynı ödevi iki kere teslim etmesin diye.
Neden CASCADE?
Ödev silinirse ona bağlı teslimler de otomatik silinsin diye.
uploads/
Bu klasör yüklenen dosyaları saklar. Ama burada en önemli mesele güvenliktir.
.htaccess örneği:
Options -Indexes
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
Bu ne işe yarar?
- dizin listelemeyi kapatır
- PHP dosyalarının çalışmasını engeller
- olası saldırıların etkisini azaltır
Güvenlik Özeti
- SQL Injection → prepared statements
- XSS → htmlspecialchars / h()
- Password → password_hash + password_verify
- Yetki → role kontrolü
- Upload → validation + güvenli klasör
- Session güvenliği → session_regenerate_id
Bu önlemler tek başına mükemmel değildir ama temel seviye için sağlam bir başlangıçtır.
Kurulum
- Projeyi WAMP içine at
- phpMyAdmin aç
- veritabanı oluştur
- SQL dosyasını içe aktar
- localhost üzerinden çalıştır
Örnek adres:
http://localhost/OdevSistem/
Sonuç:
Bu proje saf PHP + MySQL ile geliştirilmiş temel ama öğretici bir uygulamadır. Framework kullanılmamıştır. Bu yüzden sistemin mantığını doğrudan görmek için iyidir. Aynı zamanda kötü yazarsan her açığın yüzüne tokat gibi vurduğu bir projedir; yani öğrenmek için iyi, tembellik yapmak için kötü.