494 lines
22 KiB
PHP
494 lines
22 KiB
PHP
<?php
|
|
require_once('include/headscript.php');
|
|
|
|
/**
|
|
* SUPERADMIN - Gestione scuole della piattaforma
|
|
* Elenco, creazione, modifica, sospensione/riattivazione, accesso rapido.
|
|
*/
|
|
SchoolContext::requireSuperadmin();
|
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
|
|
|
if (!function_exists('e')) {
|
|
function e($value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
function sa_slugify(string $s): string
|
|
{
|
|
$t = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
|
if ($t === false) {
|
|
$t = $s;
|
|
}
|
|
$t = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $t));
|
|
return trim($t, '-');
|
|
}
|
|
|
|
if (empty($_SESSION['csrf_sa'])) {
|
|
$_SESSION['csrf_sa'] = bin2hex(random_bytes(16));
|
|
}
|
|
if (empty($_SESSION['csrf_switch'])) {
|
|
$_SESSION['csrf_switch'] = bin2hex(random_bytes(16));
|
|
}
|
|
$csrf = $_SESSION['csrf_sa'];
|
|
|
|
$fields = ['name', 'slug', 'email', 'phone', 'address', 'city', 'postcode', 'country', 'vat_number'];
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* AZIONI (POST -> redirect)
|
|
* -------------------------------------------------------------------- */
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if (!hash_equals($csrf, $_POST['token'] ?? '')) {
|
|
http_response_code(400);
|
|
die('Richiesta non valida.');
|
|
}
|
|
|
|
$action = $_POST['action'] ?? '';
|
|
$id = (int) ($_POST['idschool'] ?? 0);
|
|
|
|
try {
|
|
if ($action === 'save') {
|
|
$data = [];
|
|
foreach ($fields as $f) {
|
|
$v = trim((string) ($_POST[$f] ?? ''));
|
|
$data[$f] = ($v === '') ? null : $v;
|
|
}
|
|
|
|
if ($data['name'] === null) {
|
|
header('Location: sa-schools.php?error=name' . ($id ? '&edit=' . $id : ''));
|
|
exit;
|
|
}
|
|
|
|
$data['slug'] = sa_slugify($data['slug'] ?? $data['name']);
|
|
if ($data['slug'] === '') {
|
|
header('Location: sa-schools.php?error=slug' . ($id ? '&edit=' . $id : ''));
|
|
exit;
|
|
}
|
|
$data['country'] = strtoupper(substr($data['country'] ?? 'IT', 0, 2));
|
|
|
|
$chk = $pdo->prepare("SELECT COUNT(*) FROM schools WHERE slug = ? AND idschool <> ?");
|
|
$chk->execute([$data['slug'], $id]);
|
|
if ((int) $chk->fetchColumn() > 0) {
|
|
header('Location: sa-schools.php?error=slugdup' . ($id ? '&edit=' . $id : ''));
|
|
exit;
|
|
}
|
|
|
|
if ($id > 0) {
|
|
$set = implode(', ', array_map(fn($f) => "`$f` = :$f", $fields));
|
|
$data['idschool'] = $id;
|
|
$pdo->prepare("UPDATE schools SET $set WHERE idschool = :idschool")->execute($data);
|
|
} else {
|
|
$cols = implode(', ', array_map(fn($f) => "`$f`", $fields));
|
|
$vals = implode(', ', array_map(fn($f) => ":$f", $fields));
|
|
$pdo->prepare("INSERT INTO schools ($cols) VALUES ($vals)")->execute($data);
|
|
$id = (int) $pdo->lastInsertId();
|
|
}
|
|
|
|
// Owner opzionale: associa per email
|
|
$warn = '';
|
|
$ownerEmail = trim((string) ($_POST['owner_email'] ?? ''));
|
|
if ($ownerEmail !== '') {
|
|
$u = $pdo->prepare("SELECT id FROM auth_users WHERE email = ? LIMIT 1");
|
|
$u->execute([$ownerEmail]);
|
|
$uid = (int) $u->fetchColumn();
|
|
if ($uid > 0) {
|
|
$pdo->prepare(
|
|
"INSERT INTO school_user (user_id, idschool, school_role, active)
|
|
VALUES (?, ?, 'owner', 'Y')
|
|
ON DUPLICATE KEY UPDATE school_role = 'owner', active = 'Y'"
|
|
)->execute([$uid, $id]);
|
|
} else {
|
|
$warn = '&warn=ownernotfound';
|
|
}
|
|
}
|
|
|
|
header('Location: sa-schools.php?msg=saved' . $warn);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'toggle_status' && $id > 0) {
|
|
$pdo->prepare(
|
|
"UPDATE schools SET status = IF(status = 'suspended', 'active', 'suspended') WHERE idschool = ?"
|
|
)->execute([$id]);
|
|
header('Location: sa-schools.php?msg=status');
|
|
exit;
|
|
}
|
|
} catch (PDOException $ex) {
|
|
error_log('sa-schools: ' . $ex->getMessage());
|
|
header('Location: sa-schools.php?error=db');
|
|
exit;
|
|
}
|
|
|
|
header('Location: sa-schools.php');
|
|
exit;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* DATI
|
|
* -------------------------------------------------------------------- */
|
|
$edit = null;
|
|
if (isset($_GET['edit'])) {
|
|
$st = $pdo->prepare("SELECT * FROM schools WHERE idschool = ?");
|
|
$st->execute([(int) $_GET['edit']]);
|
|
$edit = $st->fetch() ?: null;
|
|
}
|
|
|
|
$schools = $pdo->query(
|
|
"SELECT s.*,
|
|
(SELECT COUNT(*) FROM school_user su
|
|
WHERE su.idschool = s.idschool AND su.school_role = 'student' AND su.active = 'Y') AS n_students,
|
|
(SELECT COUNT(*) FROM school_user su
|
|
WHERE su.idschool = s.idschool AND su.school_role IN ('admin','teacher') AND su.active = 'Y') AS n_staff,
|
|
(SELECT GROUP_CONCAT(CONCAT(u.first_name, ' ', u.last_name) SEPARATOR ', ')
|
|
FROM school_user su JOIN auth_users u ON u.id = su.user_id
|
|
WHERE su.idschool = s.idschool AND su.school_role = 'owner') AS owners
|
|
FROM schools s
|
|
ORDER BY s.name"
|
|
)->fetchAll();
|
|
|
|
$msgMap = [
|
|
'saved' => ['success', 'Scuola salvata correttamente.'],
|
|
'status' => ['success', 'Stato della scuola aggiornato.'],
|
|
];
|
|
$errMap = [
|
|
'name' => 'Il nome della scuola è obbligatorio.',
|
|
'slug' => 'Slug non valido.',
|
|
'slugdup' => 'Questo slug è già usato da un\'altra scuola.',
|
|
'db' => 'Errore database, operazione non eseguita.',
|
|
];
|
|
$currentId = SchoolContext::id();
|
|
?>
|
|
<!doctype html>
|
|
<html lang="it">
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>YogiBook - Scuole (piattaforma)</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="shortcut icon" href="assets/images/favicon.ico">
|
|
<link href="assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
|
|
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
|
|
<link href="assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<style>
|
|
.sa-card {
|
|
background: #fff;
|
|
border-radius: 14px;
|
|
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.06);
|
|
padding: 22px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.sa-card h5 {
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.sa-card label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
}
|
|
|
|
.school-table th {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: .03em;
|
|
color: #6b7280;
|
|
border-bottom-width: 1px;
|
|
}
|
|
|
|
.school-table td {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.school-name {
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.school-slug {
|
|
font-size: 12px;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.pill {
|
|
display: inline-block;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
padding: 3px 10px;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
.pill.active {
|
|
background: #e3f2ec;
|
|
color: #1a7f52;
|
|
}
|
|
|
|
.pill.suspended {
|
|
background: #fdecec;
|
|
color: #c0392b;
|
|
}
|
|
|
|
.pill.trial {
|
|
background: #fff4d6;
|
|
color: #8a6d00;
|
|
}
|
|
|
|
.pill.current {
|
|
background: #eef2f7;
|
|
color: #374151;
|
|
margin-left: 6px;
|
|
}
|
|
|
|
.btn-soft {
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-soft:hover {
|
|
filter: brightness(0.94);
|
|
}
|
|
|
|
.btn-soft.edit {
|
|
background: #eef2f7;
|
|
color: #374151;
|
|
}
|
|
|
|
.btn-soft.members {
|
|
background: #e8f1fb;
|
|
color: #2980b9;
|
|
}
|
|
|
|
.btn-soft.enter {
|
|
background: #e3f2ec;
|
|
color: #1a7f52;
|
|
}
|
|
|
|
.btn-soft.suspend {
|
|
background: #fdecea;
|
|
color: #c0392b;
|
|
}
|
|
|
|
.btn-soft.reactivate {
|
|
background: #fff4d6;
|
|
color: #8a6d00;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="layout-wrapper">
|
|
<header id="page-topbar" class="isvertical-topbar">
|
|
<div class="navbar-header">
|
|
<div class="d-flex">
|
|
<?php include('include/logoarea.php'); ?>
|
|
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
|
|
<i class="bx bx-menu align-middle"></i>
|
|
</button>
|
|
<div class="page-title-box align-self-center d-none d-md-block">
|
|
<h4 class="page-title mb-0">Scuole della piattaforma</h4>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex">
|
|
<?php include('include/schoolswitcher.php'); ?>
|
|
<?php include('include/languageselection.php'); ?>
|
|
<?php include('include/profiletopbar.php'); ?>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<?php include('include/sidebar.php'); ?>
|
|
|
|
<div class="main-content">
|
|
<div class="page-content">
|
|
<div class="container-fluid">
|
|
|
|
<?php if (isset($_GET['msg'], $msgMap[$_GET['msg']])) : $m = $msgMap[$_GET['msg']]; ?>
|
|
<div class="alert alert-<?php echo $m[0]; ?>"><?php echo e($m[1]); ?></div>
|
|
<?php endif; ?>
|
|
<?php if (($_GET['warn'] ?? '') === 'ownernotfound') : ?>
|
|
<div class="alert alert-warning">Scuola salvata, ma nessun utente trovato con l'email indicata come owner: l'owner deve prima registrarsi, poi puoi associarlo dalla pagina <strong>Membri</strong>.</div>
|
|
<?php endif; ?>
|
|
<?php if (isset($_GET['error'])) : ?>
|
|
<div class="alert alert-danger"><?php echo e($errMap[$_GET['error']] ?? 'Si è verificato un errore.'); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- FORM CREA / MODIFICA -->
|
|
<div class="sa-card">
|
|
<h5>
|
|
<i class="fas <?php echo $edit ? 'fa-pen' : 'fa-plus-circle'; ?> me-1" style="color:#1ebf73;"></i>
|
|
<?php echo $edit ? 'Modifica scuola: ' . e($edit['name']) : 'Nuova scuola'; ?>
|
|
</h5>
|
|
<form method="post" action="sa-schools.php">
|
|
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
|
|
<input type="hidden" name="action" value="save">
|
|
<input type="hidden" name="idschool" value="<?php echo (int) ($edit['idschool'] ?? 0); ?>">
|
|
<div class="row g-3">
|
|
<div class="col-md-5">
|
|
<label>Nome scuola *</label>
|
|
<input type="text" name="name" class="form-control" required value="<?php echo e($edit['name'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label>Slug (URL)</label>
|
|
<input type="text" name="slug" class="form-control" placeholder="auto dal nome" value="<?php echo e($edit['slug'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label>Partita IVA / CF</label>
|
|
<input type="text" name="vat_number" class="form-control" value="<?php echo e($edit['vat_number'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label>Email scuola</label>
|
|
<input type="email" name="email" class="form-control" value="<?php echo e($edit['email'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label>Telefono</label>
|
|
<input type="text" name="phone" class="form-control" value="<?php echo e($edit['phone'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-5">
|
|
<label>Indirizzo</label>
|
|
<input type="text" name="address" class="form-control" value="<?php echo e($edit['address'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label>Città</label>
|
|
<input type="text" name="city" class="form-control" value="<?php echo e($edit['city'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label>CAP</label>
|
|
<input type="text" name="postcode" class="form-control" value="<?php echo e($edit['postcode'] ?? ''); ?>">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label>Nazione</label>
|
|
<input type="text" name="country" class="form-control" maxlength="2" value="<?php echo e($edit['country'] ?? 'IT'); ?>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label>Email owner <span class="text-muted fw-normal">(facoltativa)</span></label>
|
|
<input type="email" name="owner_email" class="form-control" placeholder="utente già registrato">
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i> Salva</button>
|
|
<?php if ($edit) : ?>
|
|
<a href="sa-schools.php" class="btn btn-light">Annulla</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- ELENCO SCUOLE -->
|
|
<div class="sa-card">
|
|
<h5><i class="fas fa-building me-1" style="color:#1ebf73;"></i> Scuole (<?php echo count($schools); ?>)</h5>
|
|
<div class="table-responsive">
|
|
<table class="table school-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Scuola</th>
|
|
<th>Città</th>
|
|
<th>Owner</th>
|
|
<th class="text-center">Staff</th>
|
|
<th class="text-center">Allievi</th>
|
|
<th>Stato</th>
|
|
<th class="text-end">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($schools as $s) :
|
|
$sid = (int) $s['idschool'];
|
|
$status = $s['status'] ?: 'active';
|
|
$statusLabel = ['active' => 'Attiva', 'suspended' => 'Sospesa', 'trial' => 'Prova'][$status] ?? $status;
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<span class="school-name"><?php echo e($s['name']); ?></span>
|
|
<?php if ($sid === $currentId) : ?><span class="pill current">corrente</span><?php endif; ?>
|
|
<div class="school-slug">#<?php echo $sid; ?> · <?php echo e($s['slug']); ?></div>
|
|
</td>
|
|
<td><?php echo e($s['city'] ?? ''); ?></td>
|
|
<td>
|
|
<?php if (!empty($s['owners'])) : ?>
|
|
<?php echo e($s['owners']); ?>
|
|
<?php else : ?>
|
|
<span style="color:#e67e22;font-weight:600;">Nessun owner</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-center"><?php echo (int) $s['n_staff']; ?></td>
|
|
<td class="text-center"><?php echo (int) $s['n_students']; ?></td>
|
|
<td><span class="pill <?php echo e($status); ?>"><?php echo e($statusLabel); ?></span></td>
|
|
<td>
|
|
<div class="actions">
|
|
<a class="btn-soft edit" href="sa-schools.php?edit=<?php echo $sid; ?>"><i class="fas fa-pen"></i> Modifica</a>
|
|
<a class="btn-soft members" href="sa-school-members.php?id=<?php echo $sid; ?>"><i class="fas fa-users"></i> Membri</a>
|
|
<?php if ($sid !== $currentId && $status !== 'suspended') : ?>
|
|
<form method="post" action="switchschool.php" class="m-0">
|
|
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
|
|
<input type="hidden" name="token" value="<?php echo e($_SESSION['csrf_switch']); ?>">
|
|
<button type="submit" class="btn-soft enter"><i class="fas fa-right-to-bracket"></i> Entra</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<form method="post" action="sa-schools.php" class="m-0 form-toggle" data-name="<?php echo e($s['name']); ?>" data-suspend="<?php echo $status === 'suspended' ? '0' : '1'; ?>">
|
|
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
|
|
<input type="hidden" name="action" value="toggle_status">
|
|
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
|
|
<?php if ($status === 'suspended') : ?>
|
|
<button type="submit" class="btn-soft reactivate"><i class="fas fa-play"></i> Riattiva</button>
|
|
<?php else : ?>
|
|
<button type="submit" class="btn-soft suspend"><i class="fas fa-pause"></i> Sospendi</button>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<?php include('include/footer.php'); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
|
|
<script src="assets/libs/simplebar/simplebar.min.js"></script>
|
|
<script src="assets/libs/eva-icons/eva.min.js"></script>
|
|
<script src="assets/js/app.js"></script>
|
|
<script>
|
|
$(document).on('submit', '.form-toggle', function(e) {
|
|
e.preventDefault();
|
|
const form = this;
|
|
const suspend = $(form).data('suspend') == 1;
|
|
Swal.fire({
|
|
title: suspend ? 'Sospendere la scuola?' : 'Riattivare la scuola?',
|
|
html: '<strong>' + $('<div>').text($(form).data('name')).html() + '</strong><br><br>' +
|
|
(suspend ? 'Owner, staff e allievi non potranno più accedere a questa scuola.' : 'La scuola tornerà accessibile a tutti i suoi membri.'),
|
|
icon: suspend ? 'warning' : 'question',
|
|
showCancelButton: true,
|
|
confirmButtonColor: suspend ? '#e74c3c' : '#1ebf73',
|
|
confirmButtonText: suspend ? 'Sì, sospendi' : 'Sì, riattiva',
|
|
cancelButtonText: 'Annulla'
|
|
}).then((r) => {
|
|
if (r.isConfirmed) form.submit();
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|