# Question Paper Setting System (PHP + MySQL)
This single-file repository contains multiple files separated by headers. Save each
header-block into its own file in your project folder.
---
## [Link]
```
Simple Question Paper Setting System (PHP + MySQL)
Requirements:
- PHP 7.4+ with mysqli
- MySQL / MariaDB
- A mail or SMS gateway for OTP (this sample uses session-based OTP for demo)
Files:
- db_connect.php -- DB connection
- sql_schema.sql -- SQL schema to create necessary tables
- invigilator_login.php -- Login page (mobile + OTP)
- send_otp.php -- Generates and "sends" OTP (demo)
- verify_otp.php -- Verifies OTP and logs user in
- [Link] -- Main dashboard (choose action)
- create_paper.php -- Form to create a question paper
- save_paper.php -- Saves paper & questions
- view_papers.php -- List and view created papers
- [Link] -- Logout
- assets/[Link] -- Basic CSS
Usage:
1. Create database and import sql_schema.sql
2. Update db_connect.php with DB credentials
3. Place files in your webroot and open invigilator_login.php
NOTE: This is a straightforward, minimal, and secure-by-design example. For
production, add rate-limiting, proper SMS gateway, CSRF tokens, stronger auth,
input validation, and HTTPS.
```
---
## sql_schema.sql
```
CREATE DATABASE IF NOT EXISTS qpaper_db CHARACTER SET utf8mb4 COLLATE
utf8mb4_unicode_ci;
USE qpaper_db;
-- users (invigilators) table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
mobile VARCHAR(15) UNIQUE NOT NULL,
name VARCHAR(100) DEFAULT NULL,
role ENUM('invigilator','admin') DEFAULT 'invigilator',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- otp store (short-lived)
CREATE TABLE IF NOT EXISTS otps (
id INT AUTO_INCREMENT PRIMARY KEY,
mobile VARCHAR(15) NOT NULL,
otp_code VARCHAR(10) NOT NULL,
expires_at DATETIME NOT NULL,
used TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX(mobile)
) ENGINE=InnoDB;
-- subjects
CREATE TABLE IF NOT EXISTS subjects (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- papers
CREATE TABLE IF NOT EXISTS papers (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
category VARCHAR(50) NOT NULL, -- e.g., Question Paper, Paper Correction
exam_month VARCHAR(50) NOT NULL,
subject_id INT NOT NULL,
created_by INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (subject_id) REFERENCES subjects(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB;
-- questions
CREATE TABLE IF NOT EXISTS questions (
id INT AUTO_INCREMENT PRIMARY KEY,
paper_id INT NOT NULL,
qtext TEXT NOT NULL,
marks INT DEFAULT 1,
qtype ENUM('mcq','short','long','file') DEFAULT 'short',
extra JSON NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_id) REFERENCES papers(id) ON DELETE CASCADE
) ENGINE=InnoDB;
-- sample subject
INSERT INTO subjects (code, name) VALUES
('CS101','Computer Science - Fundamentals'),
('MA101','Mathematics - Calculus');
-- sample admin user (you can change mobile)
INSERT INTO users (mobile, name, role) VALUES
('9999999999','Admin User','admin');
```
---
## db_connect.php
```
<?php
$DB_HOST = '[Link]';
$DB_USER = 'root';
$DB_PASS = '';
$DB_NAME = 'qpaper_db';
$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME);
if ($conn->connect_error) {
die('DB Connect Error: ' . $conn->connect_error);
}
$conn->set_charset('utf8mb4');
```
---
## assets/[Link]
```
body { font-family: Arial, Helvetica, sans-serif; background:#f7f7f9; color:#222 }
.container{max-width:900px;margin:30px auto;padding:20px;background:#fff;border-
radius:8px;box-shadow:0 6px 18px rgba(0,0,0,0.06)}
.field{margin-bottom:12px}
label{display:block;margin-bottom:6px;font-weight:600}
input[type=text], input[type=tel], select,
textarea{width:100%;padding:8px;border:1px solid #ddd;border-radius:4px}
button{padding:10px 14px;border:none;border-
radius:6px;background:#0b79d0;color:white;cursor:pointer}
.table{width:100%;border-collapse:collapse}
.table th, .table td{border:1px solid #e6e6e6;padding:8px}
.header {display:flex;justify-content:space-between;align-items:center}
.small{font-size:0.9em;color:#555}
```
---
## invigilator_login.php
```
<?php
session_start();
require 'db_connect.php';
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Invigilator Login - QPaper</title>
<link rel="stylesheet" href="assets/[Link]">
</head>
<body>
<div class="container">
<div class="header"><h2>Choose Category</h2><div class="small">Question Paper |
Paper Correction | Invigilator</div></div>
<form action="send_otp.php" method="post">
<div class="field">
<label>Choose Exam Month</label>
<select name="exam_month">
<option value="Jan 2026">Jan 2026</option>
<option value="Feb 2026">Feb 2026</option>
<option value="Mar 2026">Mar 2026</option>
</select>
</div>
<div class="field">
<label>Mobile Number</label>
<input type="tel" name="mobile" placeholder="Enter mobile" required>
</div>
<div class="field">
<button type="submit">Send OTP</button>
</div>
</form>
<hr>
<form action="verify_otp.php" method="post">
<div class="field">
<label>Enter OTP</label>
<input type="text" name="otp" placeholder="6 digit OTP">
<input type="hidden" name="mobile" id="mobile_hidden">
</div>
<div class="field">
<button type="submit">Verify OTP</button>
</div>
</form>
</div>
</body>
</html>
```
---
## send_otp.php
```
<?php
session_start();
require 'db_connect.php';
$mobile = $conn->real_escape_string($_POST['mobile'] ?? '');
$exam_month = $conn->real_escape_string($_POST['exam_month'] ?? '');
if (!$mobile) { header('Location: invigilator_login.php'); exit; }
// Generate 6-digit OTP
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
$expires = (new DateTime('+5 minutes'))->format('Y-m-d H:i:s');
$stmt = $conn->prepare('INSERT INTO otps (mobile, otp_code, expires_at) VALUES
(?, ?, ?)');
$stmt->bind_param('sss', $mobile, $otp, $expires);
$stmt->execute();
// For demo: store OTP in session so user can see in verify page. In prod, send via
SMS gateway.
$_SESSION['last_otp_mobile'] = $mobile;
$_SESSION['last_otp_code'] = $otp;
$_SESSION['last_otp_expires'] = $expires;
// Ensure user exists in users table
$u = $conn->prepare('SELECT id FROM users WHERE mobile=?');
$u->bind_param('s', $mobile);
$u->execute();
$res = $u->get_result();
if ($res->num_rows === 0) {
$ins = $conn->prepare('INSERT INTO users (mobile) VALUES (?)');
$ins->bind_param('s', $mobile);
$ins->execute();
}
// redirect back to login with a small notice (demo shows OTP)
header('Location: invigilator_login.php');
exit;
```
---
## verify_otp.php
```
<?php
session_start();
require 'db_connect.php';
$mobile = $conn->real_escape_string($_POST['mobile'] ??
($_SESSION['last_otp_mobile'] ?? ''));
$otp = $conn->real_escape_string($_POST['otp'] ?? '');
if (!$mobile || !$otp) {
header('Location: invigilator_login.php'); exit;
}
$st = $conn->prepare('SELECT id, otp_code, expires_at, used FROM otps WHERE
mobile=? ORDER BY id DESC LIMIT 1');
$st->bind_param('s', $mobile);
$st->execute();
$r = $st->get_result();
if ($r->num_rows === 0) { header('Location: invigilator_login.php'); exit; }
$row = $r->fetch_assoc();
if ($row['used']) { die('OTP already used'); }
if ($row['otp_code'] !== $otp) { die('Invalid OTP'); }
if (new DateTime() > new DateTime($row['expires_at'])) { die('OTP expired'); }
// Mark used
$u = $conn->prepare('UPDATE otps SET used=1 WHERE id=?');
$u->bind_param('i', $row['id']); $u->execute();
// Log user in: fetch user id
$uq = $conn->prepare('SELECT id, name FROM users WHERE mobile=? LIMIT 1');
$uq->bind_param('s', $mobile); $uq->execute();
$ur = $uq->get_result();
$user = $ur->fetch_assoc();
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_mobile'] = $mobile;
$_SESSION['user_name'] = $user['name'] ?? 'Invigilator';
header('Location: [Link]');
exit;
```
---
## [Link]
```
<?php
session_start();
require 'db_connect.php';
if (empty($_SESSION['user_id'])) { header('Location: invigilator_login.php'); exit;
}
$user_id = (int)$_SESSION['user_id'];
// load subjects
$subs = $conn->query('SELECT * FROM subjects ORDER BY name');
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<link rel="stylesheet" href="assets/[Link]">
</head>
<body>
<div class="container">
<div class="header"><h2>Welcome, <?= htmlspecialchars($_SESSION['user_name']) ?
></h2>
<div><a href="view_papers.php">View Papers</a> | <a
href="create_paper.php">Create Paper</a> | <a href="[Link]">Logout</a></div>
</div>
<p class="small">Choose subject & start creating question paper.</p>
<form action="create_paper.php" method="get">
<div class="field">
<label>Choose Subject</label>
<select name="subject_id">
<?php while($s=$subs->fetch_assoc()): ?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['code'] . ' - ' .
$s['name']) ?></option>
<?php endwhile; ?>
</select>
</div>
<div class="field">
<button type="submit">Create Paper for Selected Subject</button>
</div>
</form>
</div>
</body>
</html>
```
---
## create_paper.php
```
<?php
session_start();
require 'db_connect.php';
if (empty($_SESSION['user_id'])) { header('Location: invigilator_login.php'); exit;
}
$subject_id = intval($_GET['subject_id'] ?? 0);
$sub = null;
if ($subject_id) {
$q = $conn->prepare('SELECT * FROM subjects WHERE id=?');
$q->bind_param('i', $subject_id); $q->execute(); $res = $q->get_result();
$sub = $res->fetch_assoc();
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Create Paper</title>
<link rel="stylesheet" href="assets/[Link]">
</head>
<body>
<div class="container">
<h3>Create Paper <?= $sub ? '- ' . htmlspecialchars($sub['name']) : '' ?></h3>
<form action="save_paper.php" method="post">
<input type="hidden" name="subject_id" value="<?= $subject_id ?>">
<div class="field"><label>Paper Title</label><input type="text" name="title"
required></div>
<div class="field"><label>Category</label>
<select name="category">
<option>Question Paper</option>
<option>Paper Correction</option>
</select>
</div>
<div class="field"><label>Exam Month</label><input type="text"
name="exam_month" placeholder="e.g. Nov 2026"></div>
<hr>
<h4>Questions</h4>
<div id="questions">
<div class="qblock">
<label>Question 1</label>
<textarea name="qtext[]" rows="3" required></textarea>
<div class="field"><label>Marks</label><input type="number" name="marks[]"
value="1"></div>
<div class="field"><label>Type</label>
<select name="qtype[]"><option value="short">Short</option><option
value="long">Long</option><option value="mcq">MCQ</option></select>
</div>
</div>
</div>
<div class="field"><button type="button" onclick="addQuestion()">Add Another
Question</button></div>
<div class="field"><button type="submit">Save Paper</button></div>
</form>
</div>
<script>
let qcount=1;
function addQuestion(){ qcount++; const div=[Link]('div');
[Link]='qblock'; [Link]=`<label>Question ${qcount}</label><textarea
name="qtext[]" rows="3" required></textarea><div
class="field"><label>Marks</label><input type="number" name="marks[]"
value="1"></div><div class="field"><label>Type</label><select
name="qtype[]"><option value="short">Short</option><option
value="long">Long</option><option value="mcq">MCQ</option></select></div>`;
[Link]('questions').appendChild(div); }
</script>
</body>
</html>
```
---
## save_paper.php
```
<?php
session_start();
require 'db_connect.php';
if (empty($_SESSION['user_id'])) { header('Location: invigilator_login.php'); exit;
}
$title = $conn->real_escape_string($_POST['title'] ?? 'Untitled');
$category = $conn->real_escape_string($_POST['category'] ?? 'Question Paper');
$exam_month = $conn->real_escape_string($_POST['exam_month'] ?? '');
$subject_id = intval($_POST['subject_id'] ?? 0);
$created_by = intval($_SESSION['user_id']);
if (!$subject_id) { die('Subject required'); }
$stmt = $conn->prepare('INSERT INTO papers (title, category, exam_month,
subject_id, created_by) VALUES (?, ?, ?, ?, ?)');
$stmt->bind_param('sssis', $title, $category, $exam_month, $subject_id,
$created_by);
$stmt->execute();
$paper_id = $stmt->insert_id;
// save questions
$qtexts = $_POST['qtext'] ?? [];
$marks = $_POST['marks'] ?? [];\$qtypes = $_POST['qtype'] ?? [];
for ($i=0;$i<count($qtexts);$i++){
$qt = $conn->real_escape_string($qtexts[$i]);
$mk = intval($marks[$i] ?? 1);
$qtpe = $conn->real_escape_string($qtypes[$i] ?? 'short');
$ins = $conn->prepare('INSERT INTO questions (paper_id, qtext, marks, qtype)
VALUES (?, ?, ?, ?)');
$ins->bind_param('isis', $paper_id, $qt, $mk, $qtpe);
$ins->execute();
}
header('Location: view_papers.php?created=1');
exit;
```
---
## view_papers.php
```
<?php
session_start();
require 'db_connect.php';
if (empty($_SESSION['user_id'])) { header('Location: invigilator_login.php'); exit;
}
$papers = $conn->query('SELECT p.*, [Link] AS scode, [Link] AS sname FROM papers p
JOIN subjects s ON p.subject_id=[Link] ORDER BY p.created_at DESC');
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>View Papers</title>
<link rel="stylesheet" href="assets/[Link]">
</head>
<body>
<div class="container">
<div class="header"><h2>Saved Papers</h2><div><a
href="[Link]">Dashboard</a> | <a href="[Link]">Logout</a></div></div>
<table class="table">
<thead><tr><th>ID</th><th>Title</th><th>Subject</th><th>Category</
th><th>Created</th><th>Action</th></tr></thead>
<tbody>
<?php while($p=$papers->fetch_assoc()): ?>
<tr>
<td><?= $p['id'] ?></td>
<td><?= htmlspecialchars($p['title']) ?></td>
<td><?= htmlspecialchars($p['scode'] . ' - ' . $p['sname']) ?></td>
<td><?= htmlspecialchars($p['category']) ?></td>
<td><?= $p['created_at'] ?></td>
<td><a href="view_paper_detail.php?id=<?= $p['id'] ?>">View</a></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</body>
</html>
```
---
## view_paper_detail.php
```
<?php
session_start(); require 'db_connect.php';
if (empty($_SESSION['user_id'])) { header('Location: invigilator_login.php'); exit;
}
$id = intval($_GET['id'] ?? 0);
$stmt = $conn->prepare('SELECT p.*, [Link] AS scode, [Link] AS sname FROM papers p
JOIN subjects s ON p.subject_id=[Link] WHERE [Link]=?');
$stmt->bind_param('i',$id); $stmt->execute(); $res = $stmt->get_result();
$paper = $res->fetch_assoc(); if (!$paper) die('Not found');
$qs = $conn->prepare('SELECT * FROM questions WHERE paper_id=? ORDER BY id'); $qs-
>bind_param('i',$id); $qs->execute(); $qres = $qs->get_result();
?>
<!doctype html>
<html><head><meta charset="utf-8"><title>Paper <?=
htmlspecialchars($paper['title']) ?></title><link rel="stylesheet"
href="assets/[Link]"></head><body>
<div class="container">
<div class="header"><h2><?= htmlspecialchars($paper['title']) ?></h2><div><a
href="view_papers.php">Back</a></div></div>
<p><strong>Subject:</strong> <?= htmlspecialchars($paper['scode'].' - '.
$paper['sname']) ?> | <strong>Category:</strong> <?=
htmlspecialchars($paper['category']) ?></p>
<hr>
<?php $i=1; while($q=$qres->fetch_assoc()): ?>
<div><strong>Q<?= $i ?> (<?= $q['marks'] ?> marks)</strong>
<p><?= nl2br(htmlspecialchars($q['qtext'])) ?></p></div>
<hr>
<?php $i++; endwhile; ?>
</div>
</body></html>
```
---
## [Link]
```
<?php
session_start(); session_unset(); session_destroy(); header('Location:
invigilator_login.php');
```
---
End of files.