Compare commits

..

1 Commits

Author SHA1 Message Date
Flatlogic Bot
c26ea67559 MAVIARcade 2025-09-26 00:28:51 +00:00
25 changed files with 1497 additions and 137 deletions

View File

@ -0,0 +1,32 @@
<?php
// admin/includes/leaderboard_functions.php
/**
* Fetches leaderboard data by joining quiz_attempts and quiz_categories.
*
* @param int $limit The maximum number of records to return.
* @return array An array of associative arrays, each representing a leaderboard entry.
*/
function get_leaderboard_data($limit = 100) {
$db = db_connect();
$sql = "
SELECT
qa.player_name,
qc.name as category_name,
qa.score,
qa.total_questions,
qa.attempt_date
FROM
quiz_attempts qa
JOIN
quiz_categories qc ON qa.category_id = qc.id
ORDER BY
qa.score DESC, qa.attempt_date DESC
LIMIT :limit
";
$stmt = $db->prepare($sql);
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
?>

View File

@ -0,0 +1,44 @@
<?php
// admin/includes/missions_functions.php
function get_all_missions() {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM missions ORDER BY id DESC");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
function get_mission_by_id($id) {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM missions WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
function add_mission($title, $description) {
$db = db_connect();
$stmt = $db->prepare("INSERT INTO missions (title, description) VALUES (:title, :description)");
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
return $stmt->execute();
}
function update_mission($id, $title, $description) {
$db = db_connect();
$stmt = $db->prepare("UPDATE missions SET title = :title, description = :description WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
return $stmt->execute();
}
function delete_mission($id) {
$db = db_connect();
$stmt = $db->prepare("DELETE FROM missions WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}

View File

@ -0,0 +1,151 @@
<?php
// admin/includes/questions_functions.php
/**
* Fetches a single quiz category by its ID to get its name.
*
* @param int $id The ID of the category.
* @return array|false The category data or false if not found.
*/
function get_quiz_category_name($id) {
$db = db_connect();
$stmt = $db->prepare("SELECT name FROM quiz_categories WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Fetches all questions for a given quiz ID.
*
* @param int $quiz_id The ID of the quiz category.
* @return array An array of questions.
*/
function get_questions_by_quiz_id($quiz_id) {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM questions WHERE category_id = :quiz_id ORDER BY id ASC");
$stmt->bindParam(':quiz_id', $quiz_id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Fetches all answers for a given question ID.
*
* @param int $question_id The ID of the question.
* @return array An array of answers.
*/
function get_answers_by_question_id($question_id) {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM answers WHERE question_id = :question_id ORDER BY id ASC");
$stmt->bindParam(':question_id', $question_id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Deletes a question and its associated answers.
*
* @param int $id The ID of the question to delete.
* @return bool True on success, false on failure.
*/
function delete_question($id) {
$db = db_connect();
// The database should handle answer deletion via ON DELETE CASCADE foreign key
$stmt = $db->prepare("DELETE FROM questions WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}
/**
* Adds a new question and its answers to the database.
*
* @param int $quiz_id The ID of the quiz category.
* @param string $question_text The text of the question.
* @param array $answers An array of answers, where each answer is an array with 'text' and 'is_correct' keys.
* @return bool True on success, false on failure.
*/
function add_question($quiz_id, $question_text, $answers) {
$db = db_connect();
try {
$db->beginTransaction();
// Insert the question
$stmt = $db->prepare("INSERT INTO questions (category_id, question_text) VALUES (:quiz_id, :question_text)");
$stmt->bindParam(':quiz_id', $quiz_id, PDO::PARAM_INT);
$stmt->bindParam(':question_text', $question_text, PDO::PARAM_STR);
$stmt->execute();
$question_id = $db->lastInsertId();
// Insert the answers
$stmt = $db->prepare("INSERT INTO answers (question_id, answer_text, is_correct) VALUES (:question_id, :answer_text, :is_correct)");
foreach ($answers as $answer) {
$stmt->bindParam(':question_id', $question_id, PDO::PARAM_INT);
$stmt->bindParam(':answer_text', $answer['text'], PDO::PARAM_STR);
$stmt->bindParam(':is_correct', $answer['is_correct'], PDO::PARAM_BOOL);
$stmt->execute();
}
$db->commit();
return true;
} catch (Exception $e) {
$db->rollBack();
return false;
}
}
/**
* Fetches a single question by its ID for editing.
*
* @param int $id The ID of the question.
* @return array|false The question data or false if not found.
*/
function get_question($id) {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM questions WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Updates a question and its answers.
*
* @param int $question_id The ID of the question to update.
* @param string $question_text The new text of the question.
* @param array $answers An array of answers to update/insert.
* @return bool
*/
function update_question($question_id, $question_text, $answers) {
$db = db_connect();
try {
$db->beginTransaction();
// Update question text
$stmt = $db->prepare("UPDATE questions SET question_text = :question_text WHERE id = :id");
$stmt->bindParam(':question_text', $question_text, PDO::PARAM_STR);
$stmt->bindParam(':id', $question_id, PDO::PARAM_INT);
$stmt->execute();
// Delete old answers
$stmt = $db->prepare("DELETE FROM answers WHERE question_id = :question_id");
$stmt->bindParam(':question_id', $question_id, PDO::PARAM_INT);
$stmt->execute();
// Insert new answers
$stmt = $db->prepare("INSERT INTO answers (question_id, answer_text, is_correct) VALUES (:question_id, :answer_text, :is_correct)");
foreach ($answers as $answer) {
$stmt->bindParam(':question_id', $question_id, PDO::PARAM_INT);
$stmt->bindParam(':answer_text', $answer['text'], PDO::PARAM_STR);
$stmt->bindParam(':is_correct', $answer['is_correct'], PDO::PARAM_BOOL);
$stmt->execute();
}
$db->commit();
return true;
} catch (Exception $e) {
$db->rollBack();
return false;
}
}
?>

View File

@ -0,0 +1,75 @@
<?php
// admin/includes/quizzes_functions.php
/**
* Fetches all quiz categories from the database.
*
* @return array An array of associative arrays, each representing a category.
*/
function get_all_quiz_categories() {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM quiz_categories ORDER BY name ASC");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Fetches a single quiz category by its ID.
*
* @param int $id The ID of the category.
* @return array|false The category data or false if not found.
*/
function get_quiz_category_by_id($id) {
$db = db_connect();
$stmt = $db->prepare("SELECT * FROM quiz_categories WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Adds a new quiz category to the database.
*
* @param string $name The name of the category.
* @param string $description The description of the category.
* @return bool True on success, false on failure.
*/
function add_quiz_category($name, $description) {
$db = db_connect();
$stmt = $db->prepare("INSERT INTO quiz_categories (name, description) VALUES (:name, :description)");
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
return $stmt->execute();
}
/**
* Updates an existing quiz category.
*
* @param int $id The ID of the category to update.
* @param string $name The new name.
* @param string $description The new description.
* @return bool True on success, false on failure.
*/
function update_quiz_category($id, $name, $description) {
$db = db_connect();
$stmt = $db->prepare("UPDATE quiz_categories SET name = :name, description = :description WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
return $stmt->execute();
}
/**
* Deletes a quiz category from the database.
*
* @param int $id The ID of the category to delete.
* @return bool True on success, false on failure.
*/
function delete_quiz_category($id) {
$db = db_connect();
$stmt = $db->prepare("DELETE FROM quiz_categories WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}
?>

49
admin/index.php Normal file
View File

@ -0,0 +1,49 @@
<?php
// admin/index.php
// --- Basic Routing ---
$page = $_GET['page'] ?? 'missions';
$allowed_pages = ['missions', 'quizzes', 'leaderboard', 'questions'];
if (!in_array($page, $allowed_pages)) {
$page = 'missions'; // Default to missions if page is not allowed
}
// --- Include necessary files ---
// General functions that are always needed
require_once __DIR__ . '/../db/config.php';
// Page-specific functions
if ($page === 'missions') {
require_once __DIR__ . '/includes/missions_functions.php';
} elseif ($page === 'quizzes') {
require_once __DIR__ . '/includes/quizzes_functions.php';
} elseif ($page === 'leaderboard') {
require_once __DIR__ . '/includes/leaderboard_functions.php';
} elseif ($page === 'questions') {
require_once __DIR__ . '/includes/questions_functions.php';
}
// --- Set Page Title ---
$page_title = ucfirst($page) . ' Management';
// --- Include Header ---
include __DIR__ . '/partials/header.php';
// --- Include Page Content ---
$page_path = __DIR__ . '/pages/' . $page . '.php';
if (file_exists($page_path)) {
include $page_path;
} else {
// Fallback for missing page files
echo "<div class='alert alert-danger'>Content file not found for this page.</div>";
}
// --- Include Footer ---
include __DIR__ . '/partials/footer.php';
?>

View File

@ -0,0 +1,39 @@
<?php
// admin/pages/leaderboard.php
$leaderboard_entries = get_leaderboard_data();
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Leaderboard</h1>
</div>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>Giocatore</th>
<th>Categoria</th>
<th>Punteggio</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<?php if (empty($leaderboard_entries)): ?>
<tr>
<td colspan="4">Nessun risultato trovato.</td>
</tr>
<?php else: ?>
<?php foreach ($leaderboard_entries as $entry): ?>
<tr>
<td><?php echo htmlspecialchars($entry['player_name']); ?></td>
<td><?php echo htmlspecialchars($entry['category_name']); ?></td>
<td><?php echo htmlspecialchars($entry['score']); ?> / <?php echo htmlspecialchars($entry['total_questions']); ?></td>
<td><?php echo htmlspecialchars(date('d/m/Y H:i', strtotime($entry['attempt_date']))); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>

116
admin/pages/missions.php Normal file
View File

@ -0,0 +1,116 @@
<?php
// admin/pages/missions.php
$action = $_GET['action'] ?? 'list';
$id = $_GET['id'] ?? null;
// Handle POST requests for add/edit
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$id = $_POST['id'] ?? null;
if ($action === 'add') {
add_mission($title, $description);
} elseif ($action === 'edit') {
update_mission($id, $title, $description);
}
// Redirect to the list view after submission
header('Location: index.php?page=missions');
exit;
}
// Handle delete action
if ($action === 'delete' && $id) {
delete_mission($id);
header('Location: index.php?page=missions');
exit;
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><?php
if ($action === 'add') echo 'Aggiungi Nuova Missione';
elseif ($action === 'edit') echo 'Modifica Missione';
else echo 'Gestione Missioni';
?></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<a href="index.php?page=missions" class="btn btn-sm btn-outline-secondary">
<span data-feather="arrow-left"></span>
Torna alla Lista
</a>
</div>
</div>
<?php if ($action === 'add' || $action === 'edit'): ?>
<?php
$mission = null;
if ($action === 'edit' && $id) {
$mission = get_mission_by_id($id);
}
?>
<form method="POST" action="index.php?page=missions&action=<?php echo $action; ?>">
<?php if ($id): ?>
<input type="hidden" name="id" value="<?php echo htmlspecialchars($id); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">Titolo Missione</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($mission['title'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Descrizione</label>
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($mission['description'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">
<?php echo $action === 'edit' ? 'Salva Modifiche' : 'Aggiungi Missione'; ?>
</button>
</form>
<?php else: // 'list' action ?>
<div class="d-flex justify-content-end mb-3">
<a href="index.php?page=missions&action=add" class="btn btn-sm btn-outline-primary">
<span data-feather="plus"></span>
Aggiungi Missione
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>Titolo</th>
<th>Descrizione</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php $missions = get_all_missions(); ?>
<?php if (empty($missions)): ?>
<tr>
<td colspan="4">Nessuna missione trovata.</td>
</tr>
<?php else: ?>
<?php foreach ($missions as $mission): ?>
<tr>
<td><?php echo htmlspecialchars($mission['id']); ?></td>
<td><?php echo htmlspecialchars($mission['title']); ?></td>
<td><?php echo htmlspecialchars($mission['description']); ?></td>
<td>
<a href="index.php?page=missions&action=edit&id=<?php echo $mission['id']; ?>" class="btn btn-sm btn-outline-secondary">
<span data-feather="edit-2"></span>
Modifica
</a>
<a href="index.php?page=missions&action=delete&id=<?php echo $mission['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Sei sicuro di voler eliminare questa missione?');">
<span data-feather="trash-2"></span>
Elimina
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>

153
admin/pages/questions.php Normal file
View File

@ -0,0 +1,153 @@
<?php
// admin/pages/questions.php
$quiz_id = $_GET['quiz_id'] ?? null;
$action = $_GET['action'] ?? 'list';
$id = $_GET['id'] ?? null;
if (!$quiz_id) {
echo "<div class='alert alert-danger'>ID Quiz non specificato.</div>";
return;
}
$quiz_category = get_quiz_category_name($quiz_id);
if (!$quiz_category) {
echo "<div class='alert alert-danger'>Categoria Quiz non trovata.</div>";
return;
}
// Handle POST requests for add/edit
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$question_text = $_POST['question_text'] ?? '';
$answers_post = $_POST['answers'] ?? [];
$correct_answer_index = $_POST['correct_answer'] ?? -1;
$id = $_POST['id'] ?? null;
$answers = [];
foreach ($answers_post as $index => $text) {
if (!empty($text)) {
$answers[] = [
'text' => $text,
'is_correct' => ($index == $correct_answer_index)
];
}
}
if ($action === 'add') {
add_question($quiz_id, $question_text, $answers);
} elseif ($action === 'edit') {
update_question($id, $question_text, $answers);
}
// Redirect to the list view after submission
header('Location: index.php?page=questions&quiz_id=' . $quiz_id);
exit;
}
// Handle delete action
if ($action === 'delete' && $id) {
delete_question($id);
header('Location: index.php?page=questions&quiz_id=' . $quiz_id);
exit;
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Domande per: <?php echo htmlspecialchars($quiz_category['name']); ?></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<a href="index.php?page=quizzes" class="btn btn-sm btn-outline-secondary">
<span data-feather="arrow-left"></span>
Torna alle Categorie
</a>
</div>
</div>
<?php if ($action === 'add' || $action === 'edit'): ?>
<?php
$question = null;
$answers = [['text'=>'', 'is_correct'=>true], ['text'=>'', 'is_correct'=>false], ['text'=>'', 'is_correct'=>false], ['text'=>'', 'is_correct'=>false]];
if ($action === 'edit' && $id) {
$question = get_question($id);
$db_answers = get_answers_by_question_id($id);
$answers = [];
foreach($db_answers as $ans) {
$answers[] = ['text' => $ans['answer_text'], 'is_correct' => $ans['is_correct']];
}
// ensure we have 4 answers to show in the form
while(count($answers) < 4) {
$answers[] = ['text' => '', 'is_correct' => false];
}
}
?>
<form method="POST" action="index.php?page=questions&quiz_id=<?php echo $quiz_id; ?>&action=<?php echo $action; ?>">
<?php if ($id): ?>
<input type="hidden" name="id" value="<?php echo htmlspecialchars($id); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="question_text" class="form-label">Testo della Domanda</label>
<textarea class="form-control" id="question_text" name="question_text" rows="3" required><?php echo htmlspecialchars($question['question_text'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">Risposte (la prima selezionata è quella corretta)</label>
<?php for ($i = 0; $i < 4; $i++): ?>
<div class="input-group mb-2">
<div class="input-group-text">
<input class="form-check-input mt-0" type="radio" name="correct_answer" value="<?php echo $i; ?>" <?php if($answers[$i]['is_correct']) echo 'checked'; ?>>
</div>
<input type="text" class="form-control" name="answers[]" value="<?php echo htmlspecialchars($answers[$i]['text'] ?? ''); ?>" required>
</div>
<?php endfor; ?>
</div>
<button type="submit" class="btn btn-primary">
<?php echo $action === 'edit' ? 'Salva Modifiche' : 'Aggiungi Domanda'; ?>
</button>
</form>
<?php else: // 'list' action ?>
<div class="d-flex justify-content-end mb-3">
<a href="index.php?page=questions&quiz_id=<?php echo $quiz_id; ?>&action=add" class="btn btn-sm btn-outline-primary">
<span data-feather="plus"></span>
Aggiungi Domanda
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>Testo della Domanda</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php $questions = get_questions_by_quiz_id($quiz_id); ?>
<?php if (empty($questions)):
?>
<tr>
<td colspan="3">Nessuna domanda trovata per questo quiz.</td>
</tr>
<?php else: ?>
<?php foreach ($questions as $question): ?>
<tr>
<td><?php echo htmlspecialchars($question['id']); ?></td>
<td><?php echo htmlspecialchars($question['question_text']); ?></td>
<td>
<a href="index.php?page=questions&quiz_id=<?php echo $quiz_id; ?>&action=edit&id=<?php echo $question['id']; ?>" class="btn btn-sm btn-outline-secondary">
<span data-feather="edit-2"></span>
Modifica
</a>
<a href="index.php?page=questions&quiz_id=<?php echo $quiz_id; ?>&action=delete&id=<?php echo $question['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Sei sicuro di voler eliminare questa domanda?');">
<span data-feather="trash-2"></span>
Elimina
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>

120
admin/pages/quizzes.php Normal file
View File

@ -0,0 +1,120 @@
<?php
// admin/pages/quizzes.php
$action = $_GET['action'] ?? 'list';
$id = $_GET['id'] ?? null;
// Handle POST requests for add/edit
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$id = $_POST['id'] ?? null;
if ($action === 'add') {
add_quiz_category($name, $description);
} elseif ($action === 'edit') {
update_quiz_category($id, $name, $description);
}
// Redirect to the list view after submission
header('Location: index.php?page=quizzes');
exit;
}
// Handle delete action
if ($action === 'delete' && $id) {
delete_quiz_category($id);
header('Location: index.php?page=quizzes');
exit;
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><?php
if ($action === 'add') echo 'Aggiungi Nuova Categoria';
elseif ($action === 'edit') echo 'Modifica Categoria';
else echo 'Gestione Quiz';
?></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<a href="index.php?page=quizzes" class="btn btn-sm btn-outline-secondary">
<span data-feather="arrow-left"></span>
Torna alla Lista
</a>
</div>
</div>
<?php if ($action === 'add' || $action === 'edit'): ?>
<?php
$category = null;
if ($action === 'edit' && $id) {
$category = get_quiz_category_by_id($id);
}
?>
<form method="POST" action="index.php?page=quizzes&action=<?php echo $action; ?>">
<?php if ($id): ?>
<input type="hidden" name="id" value="<?php echo htmlspecialchars($id); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="name" class="form-label">Nome Categoria</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($category['name'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Descrizione</label>
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($category['description'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">
<?php echo $action === 'edit' ? 'Salva Modifiche' : 'Aggiungi Categoria'; ?>
</button>
</form>
<?php else: // 'list' action ?>
<div class="d-flex justify-content-end mb-3">
<a href="index.php?page=quizzes&action=add" class="btn btn-sm btn-outline-primary">
<span data-feather="plus"></span>
Aggiungi Categoria
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Descrizione</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php $categories = get_all_quiz_categories(); ?>
<?php if (empty($categories)): ?>
<tr>
<td colspan="4">Nessuna categoria trovata.</td>
</tr>
<?php else: ?>
<?php foreach ($categories as $category): ?>
<tr>
<td><?php echo htmlspecialchars($category['id']); ?></td>
<td><?php echo htmlspecialchars($category['name']); ?></td>
<td><?php echo htmlspecialchars($category['description']); ?></td>
<td>
<a href="index.php?page=questions&quiz_id=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-info">
<span data-feather="help-circle"></span>
Domande
</a>
<a href="index.php?page=quizzes&action=edit&id=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-secondary">
<span data-feather="edit-2"></span>
Modifica
</a>
<a href="index.php?page=quizzes&action=delete&id=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Sei sicuro di voler eliminare questa categoria?');">
<span data-feather="trash-2"></span>
Elimina
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>

10
admin/partials/footer.php Normal file
View File

@ -0,0 +1,10 @@
<?php
// admin/partials/footer.php
?>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

45
admin/partials/header.php Normal file
View File

@ -0,0 +1,45 @@
<?php
// admin/partials/header.php
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MAVI Admin</title>
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body>
<div class="admin-wrapper">
<!-- Sidebar -->
<nav id="sidebar">
<div class="sidebar-header">
<h3>MAVI Admin</h3>
</div>
<ul class="list-unstyled components">
<p>Content Management</p>
<li class="<?php echo ($page === 'missions') ? 'active' : ''; ?>">
<a href="index.php?page=missions">Missions</a>
</li>
<li class="<?php echo ($page === 'quizzes') ? 'active' : ''; ?>">
<a href="index.php?page=quizzes">Quizzes</a>
</li>
<li class="<?php echo ($page === 'leaderboard') ? 'active' : ''; ?>">
<a href="index.php?page=leaderboard">Leaderboard</a>
</li>
</ul>
<ul class="list-unstyled ct-action">
<li><a href="/" class="article">Back to Public Site</a></li>
</ul>
</nav>
<!-- Page Content -->
<div id="content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
<div class="container-fluid">
<h4><?php echo $page_title ?? 'Dashboard'; ?></h4>
</div>
</nav>

6
assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,72 @@
/* assets/css/custom.css */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background-color: #F8F9FA;
}
.admin-wrapper {
display: flex;
}
#sidebar {
min-width: 250px;
max-width: 250px;
background: #2E7D32;
color: #fff;
transition: all 0.3s;
min-height: 100vh;
}
#sidebar .sidebar-header {
padding: 20px;
background: #256828;
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul p {
color: #fff;
padding: 10px;
}
#sidebar ul li a {
padding: 10px;
font-size: 1.1em;
display: block;
color: #fff;
text-decoration: none;
}
#sidebar ul li a:hover {
background: #256828;
}
#sidebar ul li.active > a,
a[aria-expanded="true"] {
background: #256828;
}
#content {
width: 100%;
padding: 20px;
}
.card {
border-radius: 0.5rem;
border: none;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.btn-primary {
background-color: #2E7D32;
border-color: #2E7D32;
}
.btn-primary:hover {
background-color: #256828;
border-color: #256828;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,17 +1,48 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_30903');
define('DB_USER', 'app_30903');
define('DB_PASS', '7d2d5a2d-6e5e-4580-a9b7-3f8e7288a494');
// db/config.php
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
return $pdo;
function db_connect() {
static $pdo;
if ($pdo) {
return $pdo;
}
$host = '127.0.0.1';
$db = 'app';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
return $pdo;
} catch (\PDOException $e) {
// In a real app, log this error and show a generic message
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
}
function run_migrations() {
$pdo = db_connect();
$migration_dir = __DIR__ . '/migrations';
$files = glob($migration_dir . '/*.sql');
sort($files);
foreach ($files as $file) {
try {
$sql = file_get_contents($file);
$pdo->exec($sql);
} catch (\PDOException $e) {
// Log error if needed, but continue
// This might happen if the table already exists, which is fine for idempotent scripts
}
}
}

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS `missions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insert some dummy data for initial view
INSERT INTO `missions` (`title`, `description`) VALUES
('The Faces of Lacedonia', 'Explore the portraits of 1957 and learn the stories of the people.'),
('A Journey Through Time', 'Compare the Lacedonia of the past with the town of today.'),
('Artisans and Crafts', 'Discover the traditional crafts and tools used by the people of Irpinia.');

View File

@ -0,0 +1,54 @@
-- Create categories table
CREATE TABLE IF NOT EXISTS `categories` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL UNIQUE
);
-- Create questions table
CREATE TABLE IF NOT EXISTS `questions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`category_id` INT NOT NULL,
`question_text` TEXT NOT NULL,
FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON DELETE CASCADE
);
-- Create answers table
CREATE TABLE IF NOT EXISTS `answers` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`question_id` INT NOT NULL,
`answer_text` VARCHAR(255) NOT NULL,
`is_correct` BOOLEAN NOT NULL DEFAULT 0,
FOREIGN KEY (`question_id`) REFERENCES `questions`(`id`) ON DELETE CASCADE
);
-- Insert categories
INSERT INTO `categories` (`name`) VALUES ('Cultura'), ('Tradizione')
ON DUPLICATE KEY UPDATE `name` = `name`;
-- Insert questions and answers for "Cultura"
-- Question 1
INSERT INTO `questions` (`category_id`, `question_text`) SELECT id, 'Quale di questi monumenti si trova a Roma?' FROM `categories` WHERE `name` = 'Cultura';
SET @last_question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@last_question_id, 'La Torre di Pisa', 0),
(@last_question_id, 'Il Colosseo', 1),
(@last_question_id, 'Il Duomo di Milano', 0),
(@last_question_id, 'Il Ponte di Rialto', 0);
-- Question 2
INSERT INTO `questions` (`category_id`, `question_text`) SELECT id, 'Chi ha scritto "La Divina Commedia"?' FROM `categories` WHERE `name` = 'Cultura';
SET @last_question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@last_question_id, 'Alessandro Manzoni', 0),
(@last_question_id, 'Giacomo Leopardi', 0),
(@last_question_id, 'Dante Alighieri', 1),
(@last_question_id, 'Giovanni Boccaccio', 0);
-- Question 3
INSERT INTO `questions` (`category_id`, `question_text`) SELECT id, 'Qual è la capitale della Francia?' FROM `categories` WHERE `name` = 'Cultura';
SET @last_question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@last_question_id, 'Berlino', 0),
(@last_question_id, 'Madrid', 0),
(@last_question_id, 'Londra', 0),
(@last_question_id, 'Parigi', 1);

View File

@ -0,0 +1,54 @@
-- Truncate tables to ensure a clean state
SET FOREIGN_KEY_CHECKS=0;
TRUNCATE TABLE `answers`;
TRUNCATE TABLE `questions`;
TRUNCATE TABLE `categories`;
SET FOREIGN_KEY_CHECKS=1;
-- Insert the new category
INSERT INTO `categories` (`id`, `name`, `description`) VALUES
(1, 'MAVI e Lacedonia', 'Domande sul Museo Antropologico Visivo Irpino e sul comune di Lacedonia');
-- Insert questions for the "MAVI e Lacedonia" category
INSERT INTO `questions` (`id`, `category_id`, `question_text`) VALUES
(1, 1, 'Dove si trova il MAVI?'),
(2, 1, 'Cosa significa l''acronimo MAVI?'),
(3, 1, 'In che anno è stato inaugurato il MAVI?'),
(4, 1, 'Quale importante fotografo americano ha documentato la vita di Lacedonia nel 1957?'),
(5, 1, 'A chi è dedicato il Museo di Lacedonia?');
-- Insert answers for the questions
-- Answers for question 1
INSERT INTO `answers` (`id`, `question_id`, `answer_text`, `is_correct`) VALUES
(1, 1, 'Lacedonia', 1),
(2, 1, 'Avellino', 0),
(3, 1, 'Napoli', 0),
(4, 1, 'Roma', 0);
-- Answers for question 2
INSERT INTO `answers` (`id`, `question_id`, `answer_text`, `is_correct`) VALUES
(5, 2, 'Museo Antropologico Visivo Irpino', 1),
(6, 2, 'Museo Archeologico Virtuale Italiano', 0),
(7, 2, 'Mostra Artistica Visiva Internazionale', 0),
(8, 2, 'Museo d''Arte Visiva Irpino', 0);
-- Answers for question 3
INSERT INTO `answers` (`id`, `question_id`, `answer_text`, `is_correct`) VALUES
(9, 3, '2008', 1),
(10, 3, '1998', 0),
(11, 3, '2018', 0),
(12, 3, '1980', 0);
-- Answers for question 4
INSERT INTO `answers` (`id`, `question_id`, `answer_text`, `is_correct`) VALUES
(13, 4, 'Frank Cancian', 1),
(14, 4, 'Robert Capa', 0),
(15, 4, 'Henri Cartier-Bresson', 0),
(16, 4, 'Ansel Adams', 0);
-- Answers for question 5
INSERT INTO `answers` (`id`, `question_id`, `answer_text`, `is_correct`) VALUES
(17, 5, 'A San Gerardo Maiella', 1),
(18, 5, 'A San Francesco d''Assisi', 0),
(19, 5, 'A Padre Pio', 0),
(20, 5, 'A San Gennaro', 0);

View File

@ -0,0 +1,65 @@
-- Inserimento della nuova categoria 'Tradizioni'
INSERT INTO `categories` (`name`) VALUES ('Tradizioni');
-- Inserimento delle domande per la categoria 'Tradizioni'
SET @category_id = (SELECT `id` FROM `categories` WHERE `name` = 'Tradizioni');
-- Domanda 1
INSERT INTO `questions` (`category_id`, `question_text`) VALUES
(@category_id, 'Come si chiamano i dolci tipici di Carnevale a Lacedonia?');
SET @question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@question_id, 'Chiacchiere', 1),
(@question_id, 'Struffoli', 0),
(@question_id, 'Cauzuncielli', 0),
(@question_id, 'Sfogliatelle', 0);
-- Domanda 2
INSERT INTO `questions` (`category_id`, `question_text`) VALUES
(@category_id, 'In che mese si svolge la sagra "cingule ricotta e vrasciol"?');
SET @question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@question_id, 'Agosto', 1),
(@question_id, 'Luglio', 0),
(@question_id, 'Settembre', 0),
(@question_id, 'Giugno', 0);
-- Domanda 3
INSERT INTO `questions` (`category_id`, `question_text`) VALUES
(@category_id, 'Quale evento storico viene rievocato a Lacedonia con un corteo in abiti d'epoca?');
SET @question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@question_id, 'La Congiura dei Baroni', 1),
(@question_id, 'La Battaglia di Lacedonia', 0),
(@question_id, 'L'arrivo di Garibaldi', 0),
(@question_id, 'La fondazione della città', 0);
-- Domanda 4
INSERT INTO `questions` (`category_id`, `question_text`) VALUES
(@category_id, 'Come si chiamano i ravioli dolci farciti con castagne e cioccolata?');
SET @question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@question_id, 'Cauzuncielli', 1),
(@question_id, 'Sfogliatelle', 0),
(@question_id, 'Chiacchiere', 0),
(@question_id, 'Struffoli', 0);
-- Domanda 5
INSERT INTO `questions` (`category_id`, `question_text`) VALUES
(@category_id, 'In onore di quale santo vengono allestiti grandi falò il 18 marzo?');
SET @question_id = LAST_INSERT_ID();
INSERT INTO `answers` (`question_id`, `answer_text`, `is_correct`) VALUES
(@question_id, 'San Giuseppe', 1),
(@question_id, 'San Gerardo', 0),
(@question_id, 'San Nicola', 0),
(@question_id, 'San Filippo Neri', 0);

View File

@ -0,0 +1,5 @@
ALTER TABLE `categories` ADD `description` TEXT NULL AFTER `name`;
UPDATE `categories` SET `description` = 'Domande sul Museo Antropologico Visivo Irpino e sul comune di Lacedonia.' WHERE `name` = 'MAVI e Lacedonia';
UPDATE `categories` SET `description` = 'Mettiti alla prova con domande sugli usi, i costumi e le tradizioni popolari.' WHERE `name` = 'Tradizioni';
UPDATE `categories` SET `description` = 'Domande sulla storia, l'arte e le curiosità legate al territorio di Lacedonia.' WHERE `name` = 'Cultura';

72
games.php Normal file
View File

@ -0,0 +1,72 @@
<?php
// games.php
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Games - MAVI Project</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">
</head>
<body>
<div class="container py-5">
<div class="text-center mb-4">
<h1 style="font-family: 'Poppins', sans-serif;">Games Hub</h1>
<p class="lead">Select a game to play.</p>
</div>
<div class="row gy-4">
<!-- Game 1: Cultural Quiz -->
<div class="col-lg-3 col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title">Quiz Culturale</h5>
<p class="card-text">Metti alla prova la tua conoscenza con domande sulla storia e la cultura locale.</p>
<a href="quiz.php" class="btn btn-primary mt-auto">Gioca</a>
</div>
</div>
</div>
<!-- Game 2: Memory Game -->
<div class="col-lg-3 col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title">Memory Game</h5>
<p class="card-text">Allena la tua memoria trovando le coppie di immagini a tema.</p>
<a href="#" class="btn btn-secondary disabled mt-auto">Gioca</a>
</div>
</div>
</div>
<!-- Game 3: Interactive Map -->
<div class="col-lg-3 col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title">Mappa Interattiva di Lacedonia</h5>
<p class="card-text">Esplora la Lacedonia del 1957 e scopri foto storiche nascoste.</p>
<a href="#" class="btn btn-secondary disabled mt-auto">Gioca</a>
</div>
</div>
</div>
<!-- Game 4: To Be Defined -->
<div class="col-lg-3 col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column text-center">
<h5 class="card-title">Gioco 4 (Da definire)</h5>
<p class="card-text">Un nuovo gioco in arrivo. Le idee sono benvenute!</p>
<a href="#" class="btn btn-secondary disabled mt-auto">Gioca</a>
</div>
</div>
</div>
</div>
<div class="text-center mt-5">
<a href="/" class="btn btn-primary">Back to Home</a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

137
index.php
View File

@ -1,131 +1,24 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
// index.php
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MAVI Landing Page</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">
</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 class="container d-flex justify-content-center align-items-center vh-100">
<div class="text-center">
<h1 style="font-family: 'Poppins', sans-serif;">Welcome to the MAVI Project</h1>
<p class="lead">The interactive learning platform for the MAVI museum.</p>
<a href="/games.php" class="btn btn-success mt-3">Play Games</a>
<a href="/admin/" class="btn btn-primary mt-3">Go to Admin Panel</a>
</div>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

44
quiz.php Normal file
View File

@ -0,0 +1,44 @@
<?php
require_once 'db/config.php';
$pdo = db_connect();
$stmt = $pdo->query("SELECT name, description FROM categories ORDER BY name");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quiz MAVI - Scegli una categoria</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">
</head>
<body>
<div class="container mt-5">
<a href="games.php" class="btn btn-secondary mb-4">Torna a Giochi</a>
<h1 class="text-center mb-4">Scegli una categoria</h1>
<div class="row justify-content-center g-4">
<?php foreach ($categories as $category): ?>
<div class="col-md-5">
<div class="card h-100 text-center">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><?php echo htmlspecialchars($category['name']); ?></h5>
<p class="card-text"><?php echo htmlspecialchars($category['description'] ?? 'Descrizione non disponibile.'); ?></p>
<div class="mt-auto">
<a href="quiz_play.php?category=<?php echo urlencode($category['name']); ?>&action=start" class="btn btn-primary">Inizia il Quiz</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="text-center mt-5">
<a href="games.php" class="btn btn-secondary">&larr; Torna alla lista dei giochi</a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

218
quiz_play.php Normal file
View File

@ -0,0 +1,218 @@
<?php
require_once 'db/config.php';
// --- Configuration ---
$num_questions_per_quiz = 5;
// --- Helper Functions ---
/**
* Fetches a single question and its answers from the database.
* @param int $question_id
* @return array|null
*/
function get_question(int $question_id): ?array {
try {
$stmt = db()->prepare("SELECT id, question_text FROM questions WHERE id = :id");
$stmt->execute(['id' => $question_id]);
$question = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$question) {
return null;
}
$stmt = db()->prepare("SELECT id, answer_text FROM answers WHERE question_id = :id ORDER BY RAND()");
$stmt->execute(['id' => $question_id]);
$question['answers'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $question;
} catch (PDOException $e) {
error_log("Error in get_question: " . $e->getMessage());
return null;
}
}
/**
* Fetches a set of random question IDs for a given category.
* @param string $category_name
* @param int $limit
* @return array
*/
function get_random_question_ids(string $category_name, int $limit): array {
try {
// First, get the category ID from the name
$cat_stmt = db()->prepare("SELECT id FROM categories WHERE name = :name");
$cat_stmt->execute(['name' => $category_name]);
$category = $cat_stmt->fetch(PDO::FETCH_ASSOC);
if (!$category) {
return [];
}
$category_id = $category['id'];
// Now, fetch the questions
$stmt = db()->prepare("SELECT id FROM questions WHERE category_id = :category_id ORDER BY RAND() LIMIT :limit");
$stmt->bindParam('category_id', $category_id, PDO::PARAM_INT);
$stmt->bindParam('limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_COLUMN);
} catch (PDOException $e) {
error_log("Error in get_random_question_ids: " . $e->getMessage());
return [];
}
}
/**
* Checks if the provided answer is correct.
* @param int $answer_id
* @return bool
*/
function is_answer_correct(int $answer_id): bool {
try {
$stmt = db()->prepare("SELECT is_correct FROM answers WHERE id = :id");
$stmt->execute(['id' => $answer_id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result && $result['is_correct'];
} catch (PDOException $e) {
error_log("Error in is_answer_correct: " . $e->getMessage());
return false;
}
}
// --- State Initialization ---
$question_ids_str = '';
$current_index = 0;
$score = 0;
$category_name = '';
$is_quiz_finished = false;
// --- Logic ---
// Is this a POST request (user answered a question)?
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Retrieve state from the form
$question_ids_str = $_POST['question_ids'] ?? '';
$current_index = isset($_POST['current_index']) ? (int)$_POST['current_index'] : 0;
$score = isset($_POST['score']) ? (int)$_POST['score'] : 0;
$submitted_answer_id = isset($_POST['answer_id']) ? (int)$_POST['answer_id'] : 0;
// Check if the answer was correct
if ($submitted_answer_id > 0 && is_answer_correct($submitted_answer_id)) {
$score++;
}
// Move to the next question
$current_index++;
}
// Or is this a GET request (start of the quiz)?
else if (isset($_GET['action']) && $_GET['action'] === 'start') {
$category_name = $_GET['category'] ?? '';
if (!empty($category_name)) {
$question_ids = get_random_question_ids($category_name, $num_questions_per_quiz);
if (count($question_ids) < $num_questions_per_quiz) {
$error_message = "Non ci sono abbastanza domande (ne servono {$num_questions_per_quiz}) in questa categoria per iniziare il quiz.";
$is_quiz_finished = true; // Stop the quiz
} else {
$question_ids_str = implode(',', $question_ids);
}
} else {
$error_message = "Nome della categoria non valido.";
$is_quiz_finished = true; // Stop the quiz
}
}
$question_ids = !empty($question_ids_str) ? explode(',', $question_ids_str) : [];
$total_questions = count($question_ids);
// Check if the quiz is over
if ($current_index >= $total_questions && $total_questions > 0) {
$is_quiz_finished = true;
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quiz in Corso - MAVI</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">
</head>
<body>
<div class="container mt-5">
<a href="quiz.php" class="btn btn-secondary mb-4">&larr; Torna alle Categorie</a>
<?php if ($is_quiz_finished): ?>
<div class="card text-center">
<div class="card-body">
<h1 class="card-title">Quiz Terminato!</h1>
<?php if (isset($error_message)): ?>
<div class="alert alert-warning"><?php echo $error_message; ?></div>
<?php else: ?>
<p class="card-text fs-4">Il tuo punteggio finale è:</p>
<p class="display-3"><strong><?php echo $score; ?> su <?php echo $total_questions; ?></strong></p>
<?php endif; ?>
<a href="quiz.php" class="btn btn-primary mt-3">Gioca Ancora</a>
</div>
</div>
<?php elseif (!empty($question_ids)): ?>
<?php
$question_id = (int)$question_ids[$current_index];
$question = get_question($question_id);
?>
<?php if ($question): ?>
<div class="card">
<div class="card-body">
<h5 class="card-title">Domanda <?php echo ($current_index + 1); ?> di <?php echo $total_questions; ?></h5>
<p class="card-text fs-4"><?php echo htmlspecialchars($question['question_text']); ?></p>
<form method="POST" action="quiz_play.php">
<?php foreach ($question['answers'] as $answer): ?>
<div class="form-check">
<input class="form-check-input" type="radio" name="answer_id" value="<?php echo $answer['id']; ?>" id="answer_<?php echo $answer['id']; ?>" required>
<label class="form-check-label" for="answer_<?php echo $answer['id']; ?>">
<?php echo htmlspecialchars($answer['answer_text']); ?>
</label>
</div>
<?php endforeach; ?>
<input type="hidden" name="question_ids" value="<?php echo htmlspecialchars($question_ids_str); ?>">
<input type="hidden" name="current_index" value="<?php echo $current_index; ?>">
<input type="hidden" name="score" value="<?php echo $score; ?>">
<button type="submit" class="btn btn-primary mt-4">Rispondi</button>
</form>
</div>
</div>
<!-- VISIBLE DEBUG INFO -->
<div class="card bg-light mt-4">
<div class="card-body">
<h6 class="card-title">Informazioni di Debug</h6>
<pre class="small"><?php echo htmlspecialchars(print_r([
'Question IDs' => $question_ids_str,
'Current Index' => $current_index,
'Score' => $score,
'Total Questions' => $total_questions
], true)); ?></pre>
</div>
</div>
<?php else: ?>
<div class="alert alert-danger">Errore: Impossibile caricare la domanda.</div>
<?php endif; ?>
<?php elseif ($_SERVER['REQUEST_METHOD'] !== 'POST'): ?>
<div class="alert alert-info">Seleziona una categoria dalla <a href="quiz.php">pagina dei quiz</a> per iniziare.</div>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>