Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20f916f31d | ||
|
|
ea73dc7483 |
60
admin_panel.php
Normal file
60
admin_panel.php
Normal 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
43
assets/css/custom.css
Normal 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);
|
||||||
|
}
|
||||||
BIN
assets/pasted-20251204-152901-9d863924.jpg
Normal file
BIN
assets/pasted-20251204-152901-9d863924.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
assets/vm-shot-2025-12-04T15-27-54-201Z.jpg
Normal file
BIN
assets/vm-shot-2025-12-04T15-27-54-201Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
163
create_test.php
Normal file
163
create_test.php
Normal 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
1
includes/footer.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
56
includes/header.php
Normal file
56
includes/header.php
Normal 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>
|
||||||
288
index.php
288
index.php
@ -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
|
|
||||||
// Read project preview data from environment
|
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<!-- Meta description -->
|
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<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;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--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>
|
</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>
|
<?php if (empty($tests)): ?>
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
<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.
|
||||||
</div>
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="row g-4">
|
||||||
|
<?php foreach ($tests as $test): ?>
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="card h-100 test-card p-3">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h5 class="card-title fw-bold"><?php echo htmlspecialchars($test['title']); ?></h5>
|
||||||
|
<p class="card-text text-secondary flex-grow-1"><?php echo htmlspecialchars($test['description']); ?></p>
|
||||||
|
<a href="take_test.php?test_id=<?php echo $test['id']; ?>" class="btn btn-primary mt-3">Start Test</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<footer class="py-5 mt-5 text-center text-secondary">
|
||||||
|
<p>© <?php echo date('Y'); ?> Psychological Testing System. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
97
login.php
Normal file
97
login.php
Normal 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>© <?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
20
logout.php
Normal 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
110
register.php
Normal 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>© <?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
52
retake_test.php
Normal 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
83
submit_test.php
Normal 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
184
take_test.php
Normal 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
67
test_results.php
Normal 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'; ?>
|
||||||
Loading…
x
Reference in New Issue
Block a user