Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
20f916f31d v3 2025-12-08 05:38:25 +00:00
Flatlogic Bot
ea73dc7483 v1 2025-12-04 08:49:26 +00:00
15 changed files with 1088 additions and 146 deletions

60
admin_panel.php Normal file
View File

@ -0,0 +1,60 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'psychologist') {
header('Location: login.php');
exit();
}
$pdo = db();
$stmt = $pdo->query("
SELECT u.username, t.title, ut.score, ut.completed_at
FROM user_tests ut
JOIN users u ON ut.user_id = u.id
JOIN tests t ON ut.test_id = t.id
ORDER BY ut.completed_at DESC
");
$results = $stmt->fetchAll();
require_once 'includes/header.php';
?>
<div class="container">
<h1 class="mt-5">Панель психолога</h1>
<p class="lead">Добро пожаловать, <?php echo htmlspecialchars($_SESSION['username']); ?>!</p>
<h2 class="mt-4">Результаты тестов</h2>
<?php if (empty($results)): ?>
<div class="alert alert-info mt-3" role="alert">
Пока нет ни одного пройденного теста.
</div>
<?php else: ?>
<div class="table-responsive mt-3">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
<th scope="col">Пользователь</th>
<th scope="col">Тест</th>
<th scope="col">Баллы</th>
<th scope="col">Дата</th>
</tr>
</thead>
<tbody>
<?php foreach ($results as $result): ?>
<tr>
<td><?php echo htmlspecialchars($result['username']); ?></td>
<td><?php echo htmlspecialchars($result['title']); ?></td>
<td><?php echo htmlspecialchars($result['score']); ?></td>
<td><?php echo date('d.m.Y H:i', strtotime($result['completed_at'])); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?php require_once 'includes/footer.php'; ?>

43
assets/css/custom.css Normal file
View File

@ -0,0 +1,43 @@
/* Psychological Testing System Custom Styles */
:root {
--primary: #3B82F6;
--secondary: #10B981;
--background: #F9FAFB;
--surface: #FFFFFF;
--text-primary: #1F2937;
--text-secondary: #4B5563;
--border: #E5E7EB;
}
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--background);
color: var(--text-primary);
}
.navbar {
background-color: var(--surface);
border-bottom: 1px solid var(--border);
}
.test-card {
background-color: var(--surface);
border: 1px solid var(--border);
border-radius: 0.5rem;
transition: all 0.2s ease-in-out;
}
.test-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.btn-primary {
background-color: var(--primary);
border-color: var(--primary);
}
.btn-primary:hover {
opacity: 0.9;
background-color: var(--primary);
border-color: var(--primary);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

163
create_test.php Normal file
View File

@ -0,0 +1,163 @@
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once 'db/config.php';
require_once 'includes/header.php';
// Block access if not logged in or not a psychologist
if (!isset($_SESSION["user_id"]) || !isset($_SESSION["role"]) || $_SESSION["role"] !== 'psychologist') {
header("Location: login.php");
exit();
}
$message = '';
$error = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$is_public = isset($_POST['is_public']) ? 1 : 0;
$questions = $_POST['questions'] ?? [];
$user_id = $_SESSION['user_id'];
if (empty($title) || empty($description) || empty($questions)) {
$error = "Please fill out the title, description, and add at least one question.";
} else {
$pdo = db();
if ($pdo) {
try {
$pdo->beginTransaction();
// Insert test
$stmt = $pdo->prepare("INSERT INTO tests (user_id, title, description, is_public) VALUES (?, ?, ?, ?)");
$stmt->execute([$user_id, $title, $description, $is_public]);
$test_id = $pdo->lastInsertId();
// Insert questions and options
foreach ($questions as $q_data) {
$question_text = trim($q_data['text'] ?? '');
if (empty($question_text)) continue;
$stmt = $pdo->prepare("INSERT INTO questions (test_id, question_text) VALUES (?, ?)");
$stmt->execute([$test_id, $question_text]);
$question_id = $pdo->lastInsertId();
$options = $q_data['options'] ?? [];
$correct_option_index = isset($q_data['correct']) ? intval($q_data['correct']) : -1;
foreach ($options as $index => $option_text) {
$option_text = trim($option_text);
if(empty($option_text)) continue;
$is_correct = ($index === $correct_option_index);
$stmt = $pdo->prepare("INSERT INTO options (question_id, option_text, is_correct) VALUES (?, ?, ?)");
$stmt->execute([$question_id, $option_text, $is_correct]);
}
}
$pdo->commit();
$message = "Test created successfully!";
} catch (PDOException $e) {
$pdo->rollBack();
$error = "Database error: " . $e->getMessage();
}
} else {
$error = "Could not connect to the database.";
}
}
}
?>
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mb-4">Create a New Test</h2>
<?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="create_test.php" method="POST" id="create-test-form">
<div class="mb-3">
<label for="title" class="form-label">Test Title</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3" required></textarea>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_public" name="is_public" value="1" checked>
<label class="form-check-label" for="is_public">Make this test public</label>
</div>
<hr class="my-4">
<h4>Questions</h4>
<div id="questions-container">
<!-- Questions will be added here dynamically -->
</div>
<button type="button" class="btn btn-secondary mt-2" id="add-question">Add Question</button>
<hr class="my-4">
<button type="submit" class="btn btn-primary">Create Test</button>
</form>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
const questionsContainer = document.getElementById('questions-container');
const addQuestionBtn = document.getElementById('add-question');
let questionCounter = 0;
addQuestionBtn.addEventListener('click', function () {
questionCounter++;
const questionId = `q_${questionCounter}`;
const questionBlock = document.createElement('div');
questionBlock.className = 'question-block border p-3 mb-3';
questionBlock.innerHTML = `
<div class="mb-2">
<label for="${questionId}_text" class="form-label fw-bold">Question ${questionCounter}</label>
<input type="text" class="form-control" id="${questionId}_text" name="questions[${questionCounter}][text]" required>
</div>
<div class="options-container ms-3">
<label class="form-label">Options</label>
<!-- Options will be added here -->
</div>
<button type="button" class="btn btn-sm btn-outline-secondary ms-3 mt-2 add-option-btn">Add Option</button>
`;
questionsContainer.appendChild(questionBlock);
});
questionsContainer.addEventListener('click', function(e) {
if (e.target && e.target.classList.contains('add-option-btn')) {
const questionBlock = e.target.closest('.question-block');
const questionIndex = Array.from(questionsContainer.children).indexOf(questionBlock);
const optionsContainer = questionBlock.querySelector('.options-container');
const optionCounter = optionsContainer.querySelectorAll('.input-group').length;
const optionBlock = document.createElement('div');
optionBlock.className = 'input-group input-group-sm mb-2';
optionBlock.innerHTML = `
<div class="input-group-text">
<input class="form-check-input mt-0" type="radio" value="${optionCounter}" name="questions[${questionIndex + 1}][correct]" required>
</div>
<input type="text" class="form-control" name="questions[${questionIndex + 1}][options][]" required>
`;
optionsContainer.appendChild(optionBlock);
}
});
});
</script>
<?php require_once 'includes/footer.php'; // Assuming you might have a footer ?>
</body>
</html>

1
includes/footer.php Normal file
View File

@ -0,0 +1 @@

56
includes/header.php Normal file
View File

@ -0,0 +1,56 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Psychological Testing System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light mb-5">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Psychological Testing System</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item">
<span class="nav-link">Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</span>
</li>
<li class="nav-item">
<a class="nav-link" href="index.php">Tests</a>
</li>
<?php if ($_SESSION['role'] === 'psychologist'): ?>
<li class="nav-item">
<a class="nav-link" href="admin_panel.php">Admin Panel</a>
</li>
<li class="nav-item">
<a class="nav-link" href="create_test.php">Create Test</a>
</li>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
<li class="nav-item">
<a class="nav-link btn btn-primary text-white" href="register.php">Register</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>

298
index.php
View File

@ -1,150 +1,156 @@
<?php <?php
declare(strict_types=1); ini_set('display_errors', 0);
@ini_set('display_errors', '1'); error_reporting(0);
@error_reporting(E_ALL);
@date_default_timezone_set('UTC'); require_once 'db/config.php';
function setupDatabase($pdo) {
try {
// Create users table
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('user', 'psychologist') NOT NULL DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);");
// Create tests table
$pdo->exec("CREATE TABLE IF NOT EXISTS tests (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
is_public BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);");
// Add user_id column to tests table if it doesn't exist
$stmt = $pdo->query("SHOW COLUMNS FROM tests LIKE 'user_id'");
if ($stmt->rowCount() == 0) {
$pdo->exec("ALTER TABLE tests ADD COLUMN user_id INT, ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;");
}
// Create questions table
$pdo->exec("CREATE TABLE IF NOT EXISTS questions (
id INT AUTO_INCREMENT PRIMARY KEY,
test_id INT NOT NULL,
question_text TEXT NOT NULL,
FOREIGN KEY (test_id) REFERENCES tests(id) ON DELETE CASCADE
);");
// Create options table
$pdo->exec("CREATE TABLE IF NOT EXISTS options (
id INT AUTO_INCREMENT PRIMARY KEY,
question_id INT NOT NULL,
option_text VARCHAR(255) NOT NULL,
FOREIGN KEY (question_id) REFERENCES questions(id) ON DELETE CASCADE
);");
// Add score column to options table if it doesn't exist
$stmt = $pdo->query("SHOW COLUMNS FROM options LIKE 'score'");
if ($stmt->rowCount() == 0) {
$pdo->exec("ALTER TABLE options ADD COLUMN score INT NOT NULL DEFAULT 0;");
}
// Create user_tests table to track completed tests and scores
$pdo->exec("CREATE TABLE IF NOT EXISTS user_tests (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
test_id INT NOT NULL,
score INT NOT NULL,
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (test_id) REFERENCES tests(id) ON DELETE CASCADE,
UNIQUE(user_id, test_id)
);");
// Create test_results_interpretations table
$pdo->exec("CREATE TABLE IF NOT EXISTS test_results_interpretations (
id INT AUTO_INCREMENT PRIMARY KEY,
test_id INT NOT NULL,
min_score INT NOT NULL,
max_score INT NOT NULL,
interpretation TEXT NOT NULL,
FOREIGN KEY (test_id) REFERENCES tests(id) ON DELETE CASCADE
);");
// Check if there's any data
$stmt = $pdo->query('SELECT COUNT(*) FROM tests');
$count = $stmt->fetchColumn();
if ($count == 0) {
// Insert dummy data
$tests = [
['title' => 'Big Five Personality Test', 'description' => 'A comprehensive test to understand your personality based on five major traits.'],
['title' => 'IQ Test - Logic & Reasoning', 'description' => 'Assess your logical reasoning and problem-solving abilities.'],
['title' => 'Myers-Briggs Type Indicator (MBTI)', 'description' => 'Discover your personality type and how you perceive the world.'],
['title' => 'Emotional Intelligence (EQ) Test', 'description' => 'Evaluate your ability to perceive, control, and evaluate emotions.'],
];
$insertStmt = $pdo->prepare('INSERT INTO tests (title, description) VALUES (?, ?)');
foreach ($tests as $test) {
$insertStmt->execute([$test['title'], $test['description']]);
}
}
} catch (PDOException $e) {
// Don't expose error details to the user
return false;
}
return true;
}
$pdo = db();
if ($pdo) {
setupDatabase($pdo);
try {
$stmt = $pdo->query('SELECT id, title, description FROM tests WHERE is_public = TRUE ORDER BY created_at DESC');
$tests = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$tests = [];
}
} else {
$tests = [];
}
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?> ?>
<!doctype html> <?php require_once 'includes/header.php'; ?>
<html lang="en">
<head> <main class="container">
<meta charset="utf-8" /> <div class="text-center mb-5">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <h1 class="fw-bold">Available Tests</h1>
<title>New Style</title> <p class="lead text-secondary">Choose a test to start your self-discovery journey.</p>
<?php </div>
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <?php if (empty($tests)): ?>
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <div class="alert alert-warning text-center" role="alert">
?> Could not connect to the database or no tests are available at the moment. Please try again later.
<?php if ($projectDescription): ?> </div>
<!-- Meta description --> <?php else: ?>
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <div class="row g-4">
<!-- Open Graph meta tags --> <?php foreach ($tests as $test): ?>
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <div class="col-md-6 col-lg-4">
<!-- Twitter meta tags --> <div class="card h-100 test-card p-3">
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <div class="card-body d-flex flex-column">
<?php endif; ?> <h5 class="card-title fw-bold"><?php echo htmlspecialchars($test['title']); ?></h5>
<?php if ($projectImageUrl): ?> <p class="card-text text-secondary flex-grow-1"><?php echo htmlspecialchars($test['description']); ?></p>
<!-- Open Graph image --> <a href="take_test.php?test_id=<?php echo $test['id']; ?>" class="btn btn-primary mt-3">Start Test</a>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> </div>
<!-- Twitter image --> </div>
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> </div>
<?php endif; ?> <?php endforeach; ?>
<link rel="preconnect" href="https://fonts.googleapis.com"> </div>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <?php endif; ?>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> </main>
<style>
:root { <footer class="py-5 mt-5 text-center text-secondary">
--bg-color-start: #6a11cb; <p>&copy; <?php echo date('Y'); ?> Psychological Testing System. All rights reserved.</p>
--bg-color-end: #2575fc; </footer>
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01); <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body> </body>
</html> </html>

97
login.php Normal file
View File

@ -0,0 +1,97 @@
<?php
session_start();
require_once 'db/config.php';
// If already logged in, redirect to index
if (isset($_SESSION['user_id'])) {
header("Location: index.php");
exit;
}
$error = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST['username']);
$password = $_POST['password'];
if (empty($username) || empty($password)) {
$error = 'Please enter both username and password.';
} else {
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, username, password, role FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Password is correct, start a new session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
// Redirect to the main page
header("Location: index.php");
exit;
} else {
$error = 'Invalid username or password.';
}
} catch (PDOException $e) {
$error = 'Database error. Please try again later.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Psychological Testing System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light mb-5">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Psychological Testing System</a>
</div>
</nav>
<main class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body p-5">
<h1 class="text-center fw-bold mb-4">Login</h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="login.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-4">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
<div class="text-center mt-4">
<p>Don't have an account? <a href="register.php">Register here</a></p>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="py-5 mt-5 text-center text-secondary">
<p>&copy; <?php echo date('Y'); ?> Psychological Testing System. All rights reserved.</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

20
logout.php Normal file
View File

@ -0,0 +1,20 @@
<?php
session_start();
// Unset all of the session variables
$_SESSION = array();
// Destroy the session.
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
// Redirect to login page
header("Location: index.php");
exit;

110
register.php Normal file
View File

@ -0,0 +1,110 @@
<?php
session_start();
require_once 'db/config.php';
$error = '';
$success = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST['username']);
$password = $_POST['password'];
$role = $_POST['role'] ?? 'user';
if (empty($username) || empty($password)) {
$error = 'Please fill in all fields.';
} elseif (strlen($password) < 6) {
$error = 'Password must be at least 6 characters long.';
} elseif ($role !== 'user' && $role !== 'psychologist') {
$error = 'Invalid role selected.';
} else {
try {
$pdo = db();
// Check if username already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
$error = 'Username already taken.';
} else {
// Hash the password
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Insert new user
$insertStmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
if ($insertStmt->execute([$username, $hashed_password, $role])) {
$success = 'Registration successful! You can now <a href="login.php">login</a>.';
} else {
$error = 'Something went wrong. Please try again later.';
}
}
} catch (PDOException $e) {
$error = 'Database error. Please try again later.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - Psychological Testing System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light mb-5">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Psychological Testing System</a>
</div>
</nav>
<main class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body p-5">
<h1 class="text-center fw-bold mb-4">Create an Account</h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php else: ?>
<form action="register.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="role" class="form-label">Register as</label>
<select class="form-select" id="role" name="role">
<option value="user" selected>User</option>
<option value="psychologist">Psychologist</option>
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>
<?php endif; ?>
<div class="text-center mt-4">
<p>Already have an account? <a href="login.php">Login here</a></p>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="py-5 mt-5 text-center text-secondary">
<p>&copy; <?php echo date('Y'); ?> Psychological Testing System. All rights reserved.</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

52
retake_test.php Normal file
View File

@ -0,0 +1,52 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
// Redirect if not logged in
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
// Check for test ID
if (!isset($_GET['test_id']) || !is_numeric($_GET['test_id'])) {
// Redirect to home or show an error
header('Location: index.php');
exit();
}
$test_id = intval($_GET['test_id']);
$user_id = $_SESSION['user_id'];
try {
$db = db();
// Start a transaction
$db->beginTransaction();
// Delete from user_answers
$stmt_answers = $db->prepare("DELETE FROM user_answers WHERE user_id = :user_id AND question_id IN (SELECT id FROM questions WHERE test_id = :test_id)");
$stmt_answers->execute(['user_id' => $user_id, 'test_id' => $test_id]);
// Delete from user_tests
$stmt_tests = $db->prepare("DELETE FROM user_tests WHERE user_id = :user_id AND test_id = :test_id");
$stmt_tests->execute(['user_id' => $user_id, 'test_id' => $test_id]);
// Commit the transaction
$db->commit();
// Redirect to the test page
header('Location: take_test.php?test_id=' . $test_id);
exit();
} catch (PDOException $e) {
// Rollback the transaction if something failed
if ($db->inTransaction()) {
$db->rollBack();
}
// You might want to log this error instead of showing it to the user
die("Database error while trying to reset the test. Please try again.");
}
// No need for footer as we are redirecting
?>

83
submit_test.php Normal file
View File

@ -0,0 +1,83 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
// Redirect if not logged in or if it's not a POST request
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: login.php');
exit();
}
// Basic validation
if (!isset($_POST['test_id']) || !is_numeric($_POST['test_id']) || !isset($_POST['answers']) || !is_array($_POST['answers'])) {
die("Invalid data submitted.");
}
$user_id = $_SESSION['user_id'];
$test_id = intval($_POST['test_id']);
$answers = $_POST['answers'];
$total_score = 0;
try {
$db = db();
// Get the scores for the selected options
$option_ids = array_values($answers);
$placeholders = implode(',', array_fill(0, count($option_ids), '?'));
$scoreStmt = $db->prepare("SELECT id, score FROM options WHERE id IN ($placeholders)");
$scoreStmt->execute($option_ids);
$scores = $scoreStmt->fetchAll(PDO::FETCH_KEY_PAIR);
// Begin transaction
$db->beginTransaction();
// Prepare statement for inserting or updating user answers
$answerStmt = $db->prepare(
"INSERT INTO user_answers (user_id, question_id, option_id) VALUES (:user_id, :question_id, :option_id)
ON DUPLICATE KEY UPDATE option_id = VALUES(option_id)"
);
// Calculate total score and insert individual answers
foreach ($answers as $question_id => $option_id) {
if (isset($scores[$option_id])) {
$total_score += $scores[$option_id];
}
// Insert the answer
$answerStmt->execute([
'user_id' => $user_id,
'question_id' => $question_id,
'option_id' => $option_id
]);
}
// Save the final score in the user_tests table
$userTestStmt = $db->prepare("
INSERT INTO user_tests (user_id, test_id, score)
VALUES (:user_id, :test_id, :score)
ON DUPLICATE KEY UPDATE score = :score
");
$userTestStmt->execute([
'user_id' => $user_id,
'test_id' => $test_id,
'score' => $total_score
]);
// Commit transaction
$db->commit();
// Redirect to results page
header("Location: test_results.php?test_id=" . $test_id);
exit();
} catch (PDOException $e) {
// Rollback on error
if ($db->inTransaction()) {
$db->rollBack();
}
// A generic error message is better for production
die("An error occurred while submitting your test. Please try again later. Error: " . $e->getMessage());
}
?>

184
take_test.php Normal file
View File

@ -0,0 +1,184 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
// Redirect if not logged in
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
// Check for test ID
if (!isset($_GET['test_id']) || !is_numeric($_GET['test_id'])) {
echo "<div class='container mt-5'><div class='alert alert-danger'>Invalid test ID.</div></div>";
require_once 'includes/footer.php';
exit();
}
$test_id = intval($_GET['test_id']);
$user_id = $_SESSION['user_id'];
try {
$db = db();
// Check if user has already taken this test
$checkStmt = $db->prepare("SELECT COUNT(*) FROM user_tests WHERE user_id = :user_id AND test_id = :test_id");
$checkStmt->execute(['user_id' => $user_id, 'test_id' => $test_id]);
if ($checkStmt->fetchColumn() > 0) {
echo "<div class='container mt-5'><div class='alert alert-info'>You have already completed this test. <a href='test_results.php?test_id=$test_id'>View your results</a>.</div></div>";
require_once 'includes/footer.php';
exit();
}
// Fetch test details
$stmt = $db->prepare("SELECT title, description FROM tests WHERE id = :id");
$stmt->execute(['id' => $test_id]);
$test = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$test) {
echo "<div class='container mt-5'><div class='alert alert-danger'>Test not found.</div></div>";
require_once 'includes/footer.php';
exit();
}
// Fetch questions and options
$questionsStmt = $db->prepare("
SELECT q.id AS question_id, q.question_text, o.id AS option_id, o.option_text, o.score
FROM questions q
JOIN options o ON q.id = o.question_id
WHERE q.test_id = :test_id
ORDER BY q.id, o.id
");
$questionsStmt->execute(['test_id' => $test_id]);
$questionsData = $questionsStmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);
$question_keys = array_keys($questionsData);
} catch (PDOException $e) {
die("Database error: " . $e->getMessage());
}
?>
<style>
.question-container {
display: none;
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.question-container.active {
display: block;
opacity: 1;
}
.progress-bar-container {
width: 100%;
background-color: #e9ecef;
border-radius: .25rem;
}
.progress-bar-fill {
height: 10px;
background-color: #28a745;
border-radius: .25rem;
transition: width 0.3s ease-in-out;
}
</style>
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h2><?php echo htmlspecialchars($test['title']); ?></h2>
</div>
<div class="card-body">
<p class="lead"><?php echo nl2br(htmlspecialchars($test['description'])); ?></p>
<hr>
<?php if (empty($questionsData)): ?>
<div class="alert alert-warning">This test has no questions yet.</div>
<?php else: ?>
<div class="progress-bar-container mb-4">
<div class="progress-bar-fill" id="progress-bar"></div>
</div>
<p id="progress-text" class="text-center"></p>
<form id="test-form" action="submit_test.php" method="post">
<input type="hidden" name="test_id" value="<?php echo $test_id; ?>">
<?php foreach ($question_keys as $index => $question_id):
$options = $questionsData[$question_id];
$is_first = ($index === 0);
$is_last = ($index === count($question_keys) - 1);
?>
<div class="question-container <?php if ($is_first) echo 'active'; ?>" data-question-index="<?php echo $index; ?>">
<div class="mb-4">
<h5><?php echo htmlspecialchars($options[0]['question_text']); ?></h5>
<?php foreach ($options as $option): ?>
<div class="form-check">
<input class="form-check-input" type="radio" name="answers[<?php echo $question_id; ?>]" id="option_<?php echo $option['option_id']; ?>" value="<?php echo $option['option_id']; ?>" required>
<label class="form-check-label" for="option_<?php echo $option['option_id']; ?>">
<?php echo htmlspecialchars($option['option_text']); ?>
</label>
</div>
<?php endforeach; ?>
</div>
<div class="d-flex justify-content-between">
<?php if (!$is_first): ?>
<button type="button" class="btn btn-secondary btn-prev">Previous</button>
<?php endif; ?>
<?php if (!$is_last): ?>
<button type="button" class="btn btn-primary btn-next ml-auto">Next</button>
<?php endif; ?>
<?php if ($is_last): ?>
<button type="submit" class="btn btn-success ml-auto">Submit Answers</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</form>
<?php endif; ?>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const questions = document.querySelectorAll('.question-container');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
let currentQuestionIndex = 0;
const totalQuestions = questions.length;
function updateProgress() {
const progressPercentage = ((currentQuestionIndex + 1) / totalQuestions) * 100;
progressBar.style.width = progressPercentage + '%';
progressText.textContent = `Question ${currentQuestionIndex + 1} of ${totalQuestions}`;
}
function showQuestion(index) {
questions.forEach((question, i) => {
if (i === index) {
question.classList.add('active');
} else {
question.classList.remove('active');
}
});
currentQuestionIndex = index;
updateProgress();
}
document.getElementById('test-form').addEventListener('click', function(e) {
if (e.target.classList.contains('btn-next')) {
const currentQuestion = questions[currentQuestionIndex];
const radio = currentQuestion.querySelector('input[type="radio"]:checked');
if(radio){
showQuestion(currentQuestionIndex + 1);
} else {
alert('Please select an answer.');
}
}
if (e.target.classList.contains('btn-prev')) {
showQuestion(currentQuestionIndex - 1);
}
});
// Initial setup
showQuestion(0);
});
</script>
<?php require_once 'includes/footer.php'; ?>

67
test_results.php Normal file
View File

@ -0,0 +1,67 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
// Redirect if not logged in
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
// Check for test ID
if (!isset($_GET['test_id']) || !is_numeric($_GET['test_id'])) {
echo "<div class='container mt-5'><div class='alert alert-danger'>Invalid test ID.</div></div>";
require_once 'includes/footer.php';
exit();
}
$test_id = intval($_GET['test_id']);
$user_id = $_SESSION['user_id'];
try {
$db = db();
// Fetch test details
$stmt = $db->prepare("SELECT title FROM tests WHERE id = :id");
$stmt->execute(['id' => $test_id]);
$test = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$test) {
echo "<div class='container mt-5'><div class='alert alert-danger'>Test not found.</div></div>";
require_once 'includes/footer.php';
exit();
}
// Fetch user's score
$scoreStmt = $db->prepare("SELECT score FROM user_tests WHERE user_id = :user_id AND test_id = :test_id");
$scoreStmt->execute(['user_id' => $user_id, 'test_id' => $test_id]);
$result = $scoreStmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Database error: " . $e->getMessage());
}
?>
<div class="container mt-5">
<div class="card text-center">
<div class="card-header">
<h2>Results for <?php echo htmlspecialchars($test['title']); ?></h2>
</div>
<div class="card-body">
<?php if ($result): ?>
<h3 class="card-title">Your Score:</h3>
<p class="display-4 fw-bold"><?php echo htmlspecialchars($result['score']); ?></p>
<p class="text-muted">This score reflects your responses to the test questions.</p>
<?php else: ?>
<div class="alert alert-warning">We couldn't find your results for this test. Please make sure you have completed it.</div>
<?php endif; ?>
</div>
<div class="card-footer text-muted">
<a href="index.php" class="btn btn-secondary">Back to Test List</a>
<a href="retake_test.php?test_id=<?php echo $test_id; ?>" class="btn btn-primary">Retake Test</a>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>