SchoolSettings + adminpanel filtrato per scuola

This commit is contained in:
2026-10-03 19:15:11 +02:00
parent 8441cc702c
commit e27413880a
9 changed files with 1460 additions and 194 deletions
+20 -8
View File
@@ -7,6 +7,7 @@ require_once('include/headscript.php');
* manuale e cancellazione completa di una classe schedulata.
*/
$pdo = DBHandlerSelect::getInstance()->getConnection();
SchoolContext::requireStaff();
function e($value): string
{
@@ -28,17 +29,24 @@ function avatarColor(string $seed): string
return $palette[$idx];
}
$optRow = $pdo->query("SELECT maxbeforetimecancell, show_teacher FROM `option` LIMIT 1")->fetch();
$timetocancel = $optRow ? (int) $optRow['maxbeforetimecancell'] : 24;
$showTeacher = ($optRow['show_teacher'] ?? 'N') === 'Y';
$timetocancel = (int) SchoolSettings::get('maxbeforetimecancell', 24);
$showTeacher = SchoolSettings::get('show_teacher', 'N') === 'Y';
$teachersById = [];
if ($showTeacher) {
foreach ($pdo->query("SELECT idemployee, nameemployee, surnameemployee, photo FROM employee")->fetchAll() as $t) {
$stT = $pdo->prepare("SELECT idemployee, nameemployee, surnameemployee, photo FROM employee WHERE idschool = ?");
$stT->execute([$idschool]);
foreach ($stT->fetchAll() as $t) {
$teachersById[(int) $t['idemployee']] = $t;
}
}
// Indirizzo della scuola (da schools), al posto del testo fisso
$schoolAddress = implode(', ', array_filter([
trim((string) ($schoolData['address'] ?? '')),
trim((string) ($schoolData['city'] ?? '')),
]));
if (isset($_GET['prev_month'])) {
$currentMonthStart = preg_replace('/[^0-9\-]/', '', $_GET['prev_month']) . '-01';
} elseif (isset($_GET['next_month'])) {
@@ -56,9 +64,10 @@ $sqlSchedule = "SELECT serviceschedule.*, service.servicename, service.colorclas
FROM serviceschedule
LEFT JOIN service ON serviceschedule.idservice = service.idservice
WHERE serviceschedule.dateschedule BETWEEN :start AND DATE_ADD(:end, INTERVAL 1 DAY)
AND serviceschedule.idschool = :idschool
ORDER BY serviceschedule.dateschedule";
$stmtSchedule = $pdo->prepare($sqlSchedule);
$stmtSchedule->execute([':start' => $currentMonthStart, ':end' => $currentMonthEnd]);
$stmtSchedule->execute([':start' => $currentMonthStart, ':end' => $currentMonthEnd, ':idschool' => $idschool]);
$scheduleRows = $stmtSchedule->fetchAll();
// Tipologie di classe presenti nel mese (per la barra filtri), con relativo colore
@@ -81,11 +90,12 @@ if (!empty($scheduleIds)) {
auth_users.first_name, auth_users.last_name, auth_users.id AS iduser
FROM bookingclass
LEFT JOIN auth_users ON bookingclass.iduser = auth_users.id
WHERE bookingclass.idserviceschedule IN ($ph)
WHERE bookingclass.idserviceschedule IN ($ph)
AND bookingclass.idschool = ?
AND bookingclass.status IN ('booked', 'pending', 'waiting')
ORDER BY bookingclass.created_at, auth_users.last_name, auth_users.first_name";
$stmtBookings = $pdo->prepare($sqlBookings);
$stmtBookings->execute(array_values($scheduleIds));
$stmtBookings->execute(array_merge(array_values($scheduleIds), [$idschool]));
foreach ($stmtBookings->fetchAll() as $b) {
$bookingsBySchedule[(int) $b['idserviceschedule']][] = $b;
}
@@ -649,7 +659,9 @@ $italianMonths = [
<?php endif; ?>
</h3>
<p class="class-meta"><i class="far fa-clock me-1"></i><?php echo e($whenFmt); ?></p>
<p class="class-meta"><i class="fas fa-location-dot me-1"></i>via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<?php if ($schoolAddress !== '') : ?>
<p class="class-meta"><i class="fas fa-location-dot me-1"></i><?php echo e($schoolAddress); ?></p>
<?php endif; ?>
<?php if ($showTeacher) :
$tch = $teachersById[(int) ($sch['idteacher'] ?? 0)] ?? null; ?>
<p class="class-meta class-teacher">
+117 -36
View File
@@ -1,17 +1,18 @@
<?php
/**
* SchoolContext v2
* Determina la scuola corrente dell'utente loggato, il suo ruolo nella scuola
* e le scuole tra cui può passare (owner multi-scuola, superadmin).
* SchoolContext v3
* - Scuola corrente dell'utente loggato e ruolo nella scuola
* - Scuole tra cui può passare (owner multi-scuola, superadmin)
* - Vista attiva (superadmin | school | teacher | student): permette a chi ha
* permessi alti di vedere l'app come un livello inferiore. Mai superiore.
*
* Uso (nella headscript):
* Uso (headscript):
* $schoolData = SchoolContext::resolve((int)$iduserlogin);
* $idschool = SchoolContext::id();
*
* Ruoli scuola: owner | admin | teacher | student
* Il superadmin di piattaforma (auth_users.is_superadmin = 'Y') può entrare
* in qualsiasi scuola: se non ne è membro, il suo ruolo risulta 'superadmin'.
* I controlli isSuperadmin()/isSchoolAdmin()/isStaff() tengono conto della
* vista attiva. viewModes() e available() usano i permessi REALI.
*/
if (!class_exists('DBHandlerSelect')) {
@@ -29,13 +30,15 @@ class SchoolContext
/** @var int */
private static $userId = 0;
/** @var bool */
/** @var bool permesso REALE di superadmin */
private static $superadmin = false;
/** @var array scuole di cui l'utente è membro */
private static $memberships = [];
/* ------------------------------------------------------------------ */
/* ================================================================== */
/* RISOLUZIONE SCUOLA */
/* ================================================================== */
public static function resolve(int $userId): array
{
@@ -77,9 +80,7 @@ class SchoolContext
return self::$school;
}
/**
* Cambia scuola corrente. Ritorna false se l'utente non può accedervi.
*/
/** Cambia scuola corrente. False se l'utente non può accedervi. */
public static function switchTo(int $idschool): bool
{
if ($idschool <= 0) {
@@ -96,10 +97,7 @@ class SchoolContext
return false;
}
/**
* Scuole tra cui l'utente può scegliere (per il selettore in topbar).
* Superadmin: tutte le scuole. Altri: solo quelle di cui è membro.
*/
/** Scuole selezionabili (permessi reali). Superadmin: tutte. */
public static function available(): array
{
if (!self::$superadmin) {
@@ -119,7 +117,70 @@ class SchoolContext
return $all;
}
/* --------------------------- getter -------------------------------- */
/* ================================================================== */
/* VISTA ATTIVA */
/* ================================================================== */
/**
* Viste consentite in base ai permessi REALI, dalla più alta alla più bassa.
* @return array chiave => etichetta
*/
public static function viewModes(): array
{
$real = self::realRole();
$modes = [];
if (self::$superadmin) {
$modes['superadmin'] = 'Superadmin';
}
if (self::$superadmin || in_array($real, ['owner', 'admin'], true)) {
$modes['school'] = 'Scuola';
}
if ($real === 'teacher') {
$modes['teacher'] = 'Insegnante';
}
$modes['student'] = 'Allievo';
return $modes;
}
/** Vista attiva (se quella in sessione non è consentita: la più alta) */
public static function viewMode(): string
{
$modes = self::viewModes();
$req = $_SESSION['view_mode'] ?? '';
if (isset($modes[$req])) {
return $req;
}
return (string) array_key_first($modes);
}
public static function setViewMode(string $mode): bool
{
if (!isset(self::viewModes()[$mode])) {
return false;
}
$_SESSION['view_mode'] = $mode;
return true;
}
/** Pagina iniziale per la vista attiva */
public static function landingPage(): string
{
switch (self::viewMode()) {
case 'superadmin':
return 'sa-schools.php';
case 'school':
case 'teacher':
return 'adminpanel.php';
default:
return 'userpanel.php';
}
}
/* ================================================================== */
/* GETTER E CONTROLLI (tengono conto della vista) */
/* ================================================================== */
public static function id(): int
{
@@ -134,64 +195,85 @@ class SchoolContext
return self::$school;
}
/** owner | admin | teacher | student | superadmin */
public static function role(): string
/** Ruolo REALE nella scuola: owner|admin|teacher|student|superadmin */
public static function realRole(): string
{
return self::get()['school_role'];
}
public static function isSuperadmin(): bool
/** Alias storico di realRole() */
public static function role(): string
{
return self::realRole();
}
public static function isRealSuperadmin(): bool
{
return self::$superadmin;
}
public static function isSuperadmin(): bool
{
return self::viewMode() === 'superadmin';
}
public static function isOwner(): bool
{
return self::role() === 'owner';
return self::realRole() === 'owner' && self::isSchoolAdmin();
}
/** Può gestire la scuola: owner, admin o superadmin */
/** Gestisce la scuola (vista superadmin o scuola) */
public static function isSchoolAdmin(): bool
{
return self::$superadmin || in_array(self::role(), ['owner', 'admin'], true);
return in_array(self::viewMode(), ['superadmin', 'school'], true);
}
/** Staff della scuola: amministratori + insegnanti */
/** Staff: amministratori + insegnanti */
public static function isStaff(): bool
{
return self::isSchoolAdmin() || self::role() === 'teacher';
return self::isSchoolAdmin() || self::viewMode() === 'teacher';
}
/** Blocca la pagina se l'utente non è amministratore della scuola */
public static function requireSchoolAdmin(): void
{
if (!self::isSchoolAdmin()) {
http_response_code(403);
die('Accesso non autorizzato.');
self::deny();
}
}
public static function requireStaff(): void
{
if (!self::isStaff()) {
self::deny();
}
}
/** Blocca la pagina se l'utente non è superadmin */
public static function requireSuperadmin(): void
{
if (!self::$superadmin) {
http_response_code(403);
die('Accesso non autorizzato.');
if (!self::isSuperadmin()) {
self::deny();
}
}
/* --------------------------- interni ------------------------------- */
/* ================================================================== */
/* INTERNI */
/* ================================================================== */
private static function deny(): void
{
http_response_code(403);
$home = self::$school ? self::landingPage() : 'userpanel.php';
die('Accesso non autorizzato con la vista attuale. <a href="' . $home . '">Torna alla pagina iniziale</a>');
}
private static function pick(int $requested): ?array
{
// 1) scuola richiesta e l'utente ne è membro
foreach (self::$memberships as $m) {
if ((int) $m['idschool'] === $requested) {
return $m;
}
}
// 2) superadmin: può entrare in qualunque scuola esistente
if (self::$superadmin) {
$pdo = self::pdo();
if ($requested > 0) {
@@ -214,7 +296,6 @@ class SchoolContext
return null;
}
// 3) prima scuola di cui è membro
return self::$memberships[0] ?? null;
}
+106
View File
@@ -0,0 +1,106 @@
<?php
/**
* SchoolSettings
* Impostazioni della scuola (tabella `option`, una riga per scuola).
* Sostituisce tutti i "SELECT * FROM `option` LIMIT 1".
*
* Uso:
* $timetocancel = (int) SchoolSettings::get('maxbeforetimecancell', 24);
* $all = SchoolSettings::all();
* SchoolSettings::save(['show_teacher' => 'Y']);
*
* Nei cron (senza sessione) passare l'idschool esplicito:
* SchoolSettings::get('show_teacher', 'N', $idschool);
*/
if (!class_exists('DBHandlerSelect')) {
require_once __DIR__ . '/db-functions.php';
}
class SchoolSettings
{
/** @var array cache per scuola: idschool => riga option */
private static $cache = [];
/** Colonne che non si possono modificare con save() */
const PROTECTED_COLUMNS = ['idption', 'idschool'];
/** Tutte le impostazioni della scuola (riga creata se mancante) */
public static function all(?int $idschool = null): array
{
$idschool = self::schoolId($idschool);
if (!isset(self::$cache[$idschool])) {
$pdo = self::pdo();
$st = $pdo->prepare("SELECT * FROM `option` WHERE idschool = ? LIMIT 1");
$st->execute([$idschool]);
$row = $st->fetch();
if (!$row) {
// Nuova scuola: crea la riga con i default delle colonne
try {
$pdo->prepare("INSERT INTO `option` (idschool) VALUES (?)")->execute([$idschool]);
$st->execute([$idschool]);
$row = $st->fetch();
} catch (PDOException $e) {
error_log('SchoolSettings: impossibile creare option per scuola ' . $idschool . ': ' . $e->getMessage());
$row = [];
}
}
self::$cache[$idschool] = $row ?: [];
}
return self::$cache[$idschool];
}
/** Singola impostazione, con default se mancante o NULL */
public static function get(string $key, $default = null, ?int $idschool = null)
{
$all = self::all($idschool);
return (array_key_exists($key, $all) && $all[$key] !== null) ? $all[$key] : $default;
}
/**
* Salva una o più impostazioni. Accetta solo colonne esistenti in `option`
* (le altre chiavi vengono ignorate).
*/
public static function save(array $data, ?int $idschool = null): void
{
$idschool = self::schoolId($idschool);
$current = self::all($idschool);
$set = [];
$params = [];
foreach ($data as $col => $val) {
if (!array_key_exists($col, $current) || in_array($col, self::PROTECTED_COLUMNS, true)) {
continue;
}
$set[] = "`$col` = ?";
$params[] = $val;
}
if (empty($set)) {
return;
}
$params[] = $idschool;
self::pdo()->prepare("UPDATE `option` SET " . implode(', ', $set) . " WHERE idschool = ?")
->execute($params);
unset(self::$cache[$idschool]);
}
private static function schoolId(?int $idschool): int
{
if ($idschool !== null && $idschool > 0) {
return $idschool;
}
return SchoolContext::id();
}
private static function pdo(): PDO
{
return DBHandlerSelect::getInstance()->getConnection();
}
}
+5 -2
View File
@@ -55,11 +55,14 @@ $_SESSION["nameuser"] = $nameuser;
$_SESSION["emailuser"] = $emailuser;
// --- Scuola corrente (SaaS) ---
require_once('class/SchoolContext.php');
require_once('class/SchoolSettings.php');
$schoolData = SchoolContext::resolve((int) $iduserlogin);
$idschool = SchoolContext::id();
$schoolname = $schoolData['name'];
$schoolrole = SchoolContext::role(); // owner|admin|teacher|student|superadmin
$isschooladmin = SchoolContext::isSchoolAdmin();
$schoolrole = SchoolContext::realRole(); // ruolo reale: owner|admin|teacher|student|superadmin
$viewmode = SchoolContext::viewMode(); // vista attiva: superadmin|school|teacher|student
$isschooladmin = SchoolContext::isSchoolAdmin(); // tiene conto della vista
$isstaff = SchoolContext::isStaff();
$issuperadmin = SchoolContext::isSuperadmin();
// ------------------------------
if (!isset($_SESSION["tempcode"])) {
+174 -146
View File
@@ -1,9 +1,120 @@
<?php
/**
* Sidebar - menu basato sulla VISTA attiva (SchoolContext::viewMode()):
* superadmin -> Piattaforma + gestione scuola
* school -> gestione scuola (owner/admin)
* teacher -> classi e prenotazioni
* student -> prenotazioni e ordini personali
* "Il mio account" è sempre visibile.
*/
$__mode = SchoolContext::viewMode();
$__modes = SchoolContext::viewModes();
$__isSuper = SchoolContext::isSuperadmin();
$__isAdmin = SchoolContext::isSchoolAdmin();
$__isStaff = SchoolContext::isStaff();
$__home = SchoolContext::landingPage();
$__page = basename($_SERVER['PHP_SELF']);
if (empty($_SESSION['csrf_switch'])) {
$_SESSION['csrf_switch'] = bin2hex(random_bytes(16));
}
$__modeIcons = [
'superadmin' => 'bx bx-crown',
'school' => 'bx bxs-school',
'teacher' => 'bx bx-chalkboard',
'student' => 'bx bx-user',
];
/** Voce di menu con evidenziazione della pagina attiva */
if (!function_exists('sb_item')) {
function sb_item(string $href, string $icon, string $label, string $current): void
{
$active = ($href === $current) ? 'mm-active' : '';
echo '<li class="' . $active . '"><a href="' . $href . '" class="' . ($active ? 'active' : '') . '">'
. '<i class="' . $icon . ' icon nav-icon"></i>'
. '<span class="menu-item">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</span></a></li>';
}
}
?>
<!-- ========== Left Sidebar Start ========== -->
<style>
.view-switcher {
padding: 14px 16px 6px;
}
.view-switcher .vs-label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
opacity: .6;
margin-bottom: 6px;
}
.view-switcher form {
display: flex;
gap: 4px;
background: rgba(0, 0, 0, .05);
border-radius: 10px;
padding: 4px;
margin: 0;
}
.view-switcher button {
flex: 1;
border: none;
background: transparent;
border-radius: 7px;
padding: 6px 4px;
font-size: 11px;
font-weight: 600;
color: inherit;
opacity: .7;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
line-height: 1.1;
}
.view-switcher button i {
font-size: 17px;
}
.view-switcher button:hover {
opacity: 1;
}
.view-switcher button.active {
background: #1ebf73;
color: #fff;
opacity: 1;
cursor: default;
}
.view-switcher .vs-school {
font-size: 12px;
font-weight: 600;
margin-top: 8px;
opacity: .75;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
body[data-sidebar-size="sm"] .view-switcher {
display: none;
}
</style>
<div class="vertical-menu">
<!-- LOGO -->
<div class="navbar-brand-box">
<a href="index.html" class="logo logo-dark">
<a href="<?php echo $__home; ?>" class="logo logo-dark">
<span class="logo-sm">
<img src="assets/images/logo-dark-sm.png" alt="" height="30">
</span>
@@ -12,7 +123,7 @@
</span>
</a>
<a href="index.html" class="logo logo-light">
<a href="<?php echo $__home; ?>" class="logo logo-light">
<span class="logo-lg">
<img src="assets/images/logo-light.png" alt="" height="30">
</span>
@@ -28,169 +139,86 @@
<div data-simplebar class="sidebar-menu-scroll">
<?php if (count($__modes) > 1) : ?>
<!-- Selettore vista -->
<div class="view-switcher">
<div class="vs-label">Vista</div>
<form method="post" action="switchview.php">
<input type="hidden" name="token" value="<?php echo $_SESSION['csrf_switch']; ?>">
<?php foreach ($__modes as $__k => $__lbl) : ?>
<button type="submit" name="mode" value="<?php echo $__k; ?>"
class="<?php echo $__k === $__mode ? 'active' : ''; ?>"
<?php echo $__k === $__mode ? 'disabled' : ''; ?>
title="Vedi come <?php echo htmlspecialchars($__lbl, ENT_QUOTES, 'UTF-8'); ?>">
<i class="<?php echo $__modeIcons[$__k] ?? 'bx bx-show'; ?>"></i>
<?php echo htmlspecialchars($__lbl, ENT_QUOTES, 'UTF-8'); ?>
</button>
<?php endforeach; ?>
</form>
<div class="vs-school" title="Scuola corrente">
<i class="bx bx-buildings"></i> <?php echo htmlspecialchars(SchoolContext::get()['name'], ENT_QUOTES, 'UTF-8'); ?>
</div>
</div>
<?php endif; ?>
<!--- Sidemenu -->
<div id="sidebar-menu">
<!-- Left Menu Start -->
<ul class="metismenu list-unstyled" id="side-menu">
<li class="menu-title" data-key="t-menu">Dashboard</li>
<?php if ($__isSuper) : ?>
<!-- Piattaforma -->
<li class="menu-title">Piattaforma</li>
<?php sb_item('sa-schools.php', 'bx bx-buildings', 'Scuole', $__page); ?>
<?php endif; ?>
<li>
<a href="userpanel.php" class="">
<i class="bx bx-calendar-event icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Prenotazioni</span>
</a>
</li>
<li>
<a href="orders.php" class="">
<i class="bx bx-detail icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Ordini</span>
</a>
</li>
<li class="menu-title" data-key="t-applications">Profilo Utente</li>
<li>
<a href="userprofile.php" class="">
<i class="bx bx-user-circle icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Profilo utente</span>
</a>
</li>
<li>
<a href="options.php" class="">
<i class="bx bx-cog icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Opzioni</span>
</a>
</li>
<li>
<a href="userprofiledoc.php" class="">
<i class="bx bxs-file-doc icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Certificati</span>
</a>
</li>
<?php if (Auth::user()->hasRole('Admin')): ?>
<?php if (!$__isStaff) : ?>
<!-- Area allievo -->
<li class="menu-title">Le mie lezioni</li>
<?php sb_item('userpanel.php', 'bx bx-calendar-event', 'Prenotazioni', $__page); ?>
<?php sb_item('orders.php', 'bx bx-detail', 'Ordini', $__page); ?>
<?php endif; ?>
<?php if ($__isStaff) : ?>
<!-- Classi & Prenotazioni -->
<li class="menu-title" data-key="t-admin-classi">Classi &amp; Prenotazioni</li>
<li>
<a href="adminpanel.php" class="">
<i class="bx bxs-school icon nav-icon"></i>
<span class="menu-item">Classi</span>
</a>
</li>
<li>
<a href="admin-services.php" class="">
<i class="bx bx-edit-alt icon nav-icon"></i>
<span class="menu-item">Servizi</span>
</a>
</li>
<li>
<a href="pendingbookedclass.php" class="">
<i class="bx bx-time-five icon nav-icon"></i>
<span class="menu-item">Pending/Waiting</span>
</a>
</li>
<li>
<a href="dayoff.php" class="">
<i class="bx bx-power-off icon nav-icon"></i>
<span class="menu-item">Day Off</span>
</a>
</li>
<li class="menu-title">Classi &amp; Prenotazioni</li>
<?php sb_item('adminpanel.php', 'bx bxs-school', 'Classi', $__page); ?>
<?php if ($__isAdmin) sb_item('admin-services.php', 'bx bx-edit-alt', 'Servizi', $__page); ?>
<?php sb_item('pendingbookedclass.php', 'bx bx-time-five', 'Pending/Waiting', $__page); ?>
<?php if ($__isAdmin) sb_item('dayoff.php', 'bx bx-power-off', 'Day Off', $__page); ?>
<?php endif; ?>
<?php if ($__isAdmin) : ?>
<!-- Eventi -->
<li class="menu-title" data-key="t-admin-eventi">Eventi</li>
<li class="menu-title">Eventi</li>
<?php sb_item('events.php', 'bx bx-calendar-star', 'Eventi', $__page); ?>
<?php sb_item('event_locations.php', 'bx bx-map', 'Sedi', $__page); ?>
<?php sb_item('event_options.php', 'bx bx-cog', 'Opzioni Eventi', $__page); ?>
<li>
<a href="events.php" class="">
<i class="bx bx-calendar-star icon nav-icon"></i>
<span class="menu-item">Eventi</span>
</a>
</li>
<li>
<a href="event_locations.php" class="">
<i class="bx bx-map icon nav-icon"></i>
<span class="menu-item">Sedi</span>
</a>
</li>
<li>
<a href="event_options.php" class="">
<i class="bx bx-map icon nav-icon"></i>
<span class="menu-item">Opzione Eventi</span>
</a>
</li>
<!-- Ordini -->
<li class="menu-title" data-key="t-admin-ordini">Ordini</li>
<li>
<a href="orderbooklist.php" class="">
<i class="bx bx-list-check icon nav-icon"></i>
<span class="menu-item">Order Pending</span>
</a>
</li>
<li>
<a href="ordersummary.php" class="">
<i class="bx bx-receipt icon nav-icon"></i>
<span class="menu-item">Sommario Ordini</span>
</a>
</li>
<li class="menu-title">Ordini</li>
<?php sb_item('orderbooklist.php', 'bx bx-list-check', 'Ordini Pending', $__page); ?>
<?php sb_item('ordersummary.php', 'bx bx-receipt', 'Sommario Ordini', $__page); ?>
<!-- Report & Certificati -->
<li class="menu-title" data-key="t-admin-report">Report &amp; Certificati</li>
<li class="menu-title">Report &amp; Certificati</li>
<?php sb_item('situationusers.php', 'bx bx-bar-chart-alt-2', 'Situazione', $__page); ?>
<?php sb_item('certificati.php', 'bx bx-certification', 'Certificati', $__page); ?>
<?php sb_item('statistiche.php', 'bx bx-line-chart', 'Statistiche', $__page); ?>
<li>
<a href="situationusers.php" class="">
<i class="bx bx-bar-chart-alt-2 icon nav-icon"></i>
<span class="menu-item">Situazione</span>
</a>
</li>
<li>
<a href="certificati.php" class="">
<i class="bx bx-certification icon nav-icon"></i>
<span class="menu-item">Certificati</span>
</a>
</li>
<li>
<a href="statistiche.php" class="">
<i class="bx bx-certification icon nav-icon"></i>
<span class="menu-item">Statistiche</span>
</a>
</li>
<!-- Impostazioni -->
<li class="menu-title" data-key="t-admin-settings">Impostazioni</li>
<li>
<a href="optionsschool.php" class="">
<i class="bx bx-slider-alt icon nav-icon"></i>
<span class="menu-item">Opzioni Scuola</span>
</a>
</li>
<li>
<a href="insegnanti.php" class="">
<i class="bx bx-slider-alt icon nav-icon"></i>
<span class="menu-item">Insegnanti</span>
</a>
</li>
<!-- Template (demo) -->
<li class="menu-title" data-key="t-applications">Template</li>
<li>
<a href="javascript: void(0);" class="has-arrow">
<i class="bx bx-table icon nav-icon"></i>
<span class="menu-item" data-key="t-tables">Tables</span>
</a>
<ul class="sub-menu" aria-expanded="false">
<li><a href="copiaintegra/dist/tables-basic.html" data-key="t-basic-tables">Basic Tables</a></li>
<li><a href="copiaintegra/dist/tables-advanced.html" data-key="t-advanced-tables">Advance Tables</a></li>
</ul>
</li>
<li class="menu-title">Impostazioni scuola</li>
<?php sb_item('optionsschool.php', 'bx bx-slider-alt', 'Opzioni Scuola', $__page); ?>
<?php sb_item('insegnanti.php', 'bx bx-group', 'Insegnanti', $__page); ?>
<?php endif; ?>
<!-- Account personale: sempre visibile -->
<li class="menu-title">Il mio account</li>
<?php sb_item('userprofile.php', 'bx bx-user-circle', 'Profilo utente', $__page); ?>
<?php sb_item('userprofiledoc.php', 'bx bxs-file-doc', 'Certificati', $__page); ?>
<?php sb_item('options.php', 'bx bx-cog', 'Opzioni', $__page); ?>
</ul>
</div>
<!-- Sidebar -->
</div>
</div>
<!-- Left Sidebar End -->
+517
View File
@@ -0,0 +1,517 @@
<?php
require_once('include/headscript.php');
/**
* SUPERADMIN - Membri di una scuola (school_user)
* Aggiunta per email, cambio ruolo, attiva/disattiva, rimozione.
* Protezione: una scuola non può restare senza owner attivo.
*/
SchoolContext::requireSuperadmin();
$pdo = DBHandlerSelect::getInstance()->getConnection();
if (!function_exists('e')) {
function e($value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
}
if (empty($_SESSION['csrf_sa'])) {
$_SESSION['csrf_sa'] = bin2hex(random_bytes(16));
}
$csrf = $_SESSION['csrf_sa'];
$roles = [
'owner' => 'Titolare',
'admin' => 'Admin',
'teacher' => 'Insegnante',
'student' => 'Allievo',
];
$sid = (int) ($_GET['id'] ?? $_POST['idschool'] ?? 0);
$st = $pdo->prepare("SELECT * FROM schools WHERE idschool = ?");
$st->execute([$sid]);
$school = $st->fetch();
if (!$school) {
header('Location: sa-schools.php');
exit;
}
$back = 'sa-school-members.php?id=' . $sid;
/** Owner attivi della scuola, escluso eventualmente un membro */
function activeOwnersExcept(PDO $pdo, int $sid, int $exceptId): int
{
$q = $pdo->prepare(
"SELECT COUNT(*) FROM school_user
WHERE idschool = ? AND school_role = 'owner' AND active = 'Y' AND idschooluser <> ?"
);
$q->execute([$sid, $exceptId]);
return (int) $q->fetchColumn();
}
/** Riga membro (solo se appartiene a questa scuola) */
function memberRow(PDO $pdo, int $sid, int $idsu): ?array
{
$q = $pdo->prepare("SELECT * FROM school_user WHERE idschooluser = ? AND idschool = ?");
$q->execute([$idsu, $sid]);
return $q->fetch() ?: null;
}
/* ----------------------------------------------------------------------
* AZIONI
* -------------------------------------------------------------------- */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($csrf, $_POST['token'] ?? '')) {
http_response_code(400);
die('Richiesta non valida.');
}
$action = $_POST['action'] ?? '';
$idsu = (int) ($_POST['idschooluser'] ?? 0);
try {
if ($action === 'add') {
$email = trim((string) ($_POST['email'] ?? ''));
$role = $_POST['role'] ?? 'student';
if (!isset($roles[$role]) || $email === '') {
header("Location: $back&error=data");
exit;
}
$u = $pdo->prepare("SELECT id FROM auth_users WHERE email = ? LIMIT 1");
$u->execute([$email]);
$uid = (int) $u->fetchColumn();
if ($uid === 0) {
header("Location: $back&error=notfound");
exit;
}
$pdo->prepare(
"INSERT INTO school_user (user_id, idschool, school_role, active)
VALUES (?, ?, ?, 'Y')
ON DUPLICATE KEY UPDATE school_role = ?, active = 'Y'"
)->execute([$uid, $sid, $role, $role]);
header("Location: $back&msg=added");
exit;
}
$row = $idsu > 0 ? memberRow($pdo, $sid, $idsu) : null;
if (!$row) {
header("Location: $back&error=data");
exit;
}
$isActiveOwner = ($row['school_role'] === 'owner' && $row['active'] === 'Y');
if ($action === 'role') {
$role = $_POST['role'] ?? '';
if (!isset($roles[$role])) {
header("Location: $back&error=data");
exit;
}
if ($isActiveOwner && $role !== 'owner' && activeOwnersExcept($pdo, $sid, $idsu) === 0) {
header("Location: $back&error=lastowner");
exit;
}
$pdo->prepare("UPDATE school_user SET school_role = ? WHERE idschooluser = ? AND idschool = ?")
->execute([$role, $idsu, $sid]);
header("Location: $back&msg=role");
exit;
}
if ($action === 'toggle') {
if ($isActiveOwner && activeOwnersExcept($pdo, $sid, $idsu) === 0) {
header("Location: $back&error=lastowner");
exit;
}
$pdo->prepare("UPDATE school_user SET active = IF(active = 'Y', 'N', 'Y') WHERE idschooluser = ? AND idschool = ?")
->execute([$idsu, $sid]);
header("Location: $back&msg=toggled");
exit;
}
if ($action === 'remove') {
if ($isActiveOwner && activeOwnersExcept($pdo, $sid, $idsu) === 0) {
header("Location: $back&error=lastowner");
exit;
}
$pdo->prepare("DELETE FROM school_user WHERE idschooluser = ? AND idschool = ?")
->execute([$idsu, $sid]);
header("Location: $back&msg=removed");
exit;
}
} catch (PDOException $ex) {
error_log('sa-school-members: ' . $ex->getMessage());
header("Location: $back&error=db");
exit;
}
header("Location: $back");
exit;
}
/* ----------------------------------------------------------------------
* ELENCO (filtri: ruolo, ricerca)
* -------------------------------------------------------------------- */
$fRole = $_GET['role'] ?? '';
$fQ = trim((string) ($_GET['q'] ?? ''));
$where = ["su.idschool = ?"];
$params = [$sid];
if (isset($roles[$fRole])) {
$where[] = "su.school_role = ?";
$params[] = $fRole;
}
if ($fQ !== '') {
$where[] = "(u.first_name LIKE ? OR u.last_name LIKE ? OR u.email LIKE ?)";
$like = '%' . $fQ . '%';
array_push($params, $like, $like, $like);
}
$sql = "SELECT su.*, u.first_name, u.last_name, u.email
FROM school_user su
JOIN auth_users u ON u.id = su.user_id
WHERE " . implode(' AND ', $where) . "
ORDER BY FIELD(su.school_role, 'owner', 'admin', 'teacher', 'student'), u.last_name, u.first_name
LIMIT 300";
$stm = $pdo->prepare($sql);
$stm->execute($params);
$members = $stm->fetchAll();
$cnt = $pdo->prepare("SELECT school_role, COUNT(*) AS n FROM school_user WHERE idschool = ? GROUP BY school_role");
$cnt->execute([$sid]);
$counts = array_column($cnt->fetchAll(), 'n', 'school_role');
$msgMap = [
'added' => 'Membro aggiunto.',
'role' => 'Ruolo aggiornato.',
'toggled' => 'Stato del membro aggiornato.',
'removed' => 'Membro rimosso dalla scuola.',
];
$errMap = [
'data' => 'Dati non validi.',
'notfound' => 'Nessun utente registrato con questa email. L\'utente deve prima registrarsi.',
'lastowner' => 'Operazione bloccata: la scuola resterebbe senza un titolare attivo. Nomina prima un altro owner.',
'db' => 'Errore database, operazione non eseguita.',
];
$roleColors = [
'owner' => ['#fff4d6', '#8a6d00'],
'admin' => ['#e8f1fb', '#2980b9'],
'teacher' => ['#f1e8fb', '#8e44ad'],
'student' => ['#e3f2ec', '#1a7f52'],
];
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Membri <?php echo e($school['name']); ?></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;
}
.count-chips {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.count-chip {
border: 1px solid #e2e4e9;
background: #fff;
color: #374151;
font-size: 13px;
font-weight: 600;
padding: 6px 14px;
border-radius: 20px;
text-decoration: none;
}
.count-chip.active,
.count-chip:hover {
background: #1ebf73;
border-color: #1ebf73;
color: #fff;
}
.member-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: 10px;
background: #f8fafc;
border: 1px solid #eef0f3;
margin-bottom: 8px;
}
.member-row.inactive {
opacity: .55;
}
.avatar {
flex: 0 0 38px;
width: 38px;
height: 38px;
border-radius: 50%;
background: #E1D0B8;
color: #5a4630;
font-weight: 800;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
.member-info {
flex: 1;
min-width: 0;
}
.member-name {
font-weight: 600;
color: #1f2937;
}
.member-email {
font-size: 12px;
color: #6b7280;
}
.role-pill {
font-size: 11px;
font-weight: 700;
padding: 2px 9px;
border-radius: 20px;
margin-left: 6px;
}
.member-actions {
display: flex;
gap: 6px;
align-items: center;
flex-shrink: 0;
}
.member-actions select {
font-size: 12px;
padding: 4px 8px;
height: auto;
width: auto;
}
.btn-soft {
border: none;
border-radius: 8px;
padding: 6px 11px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.btn-soft.toggle {
background: #eef2f7;
color: #374151;
}
.btn-soft.remove {
background: #fdecea;
color: #c0392b;
}
</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">Membri: <?php echo e($school['name']); ?></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">
<a href="sa-schools.php" class="d-inline-block mb-3"><i class="fas fa-arrow-left me-1"></i> Torna alle scuole</a>
<?php if (isset($_GET['msg'], $msgMap[$_GET['msg']])) : ?>
<div class="alert alert-success"><?php echo e($msgMap[$_GET['msg']]); ?></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; ?>
<!-- AGGIUNGI MEMBRO -->
<div class="sa-card">
<h5><i class="fas fa-user-plus me-1" style="color:#1ebf73;"></i> Aggiungi membro</h5>
<form method="post" action="sa-school-members.php" class="row g-2 align-items-end">
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
<input type="hidden" name="action" value="add">
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
<div class="col-md-6">
<label>Email utente registrato</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="col-md-3">
<label>Ruolo</label>
<select name="role" class="form-select">
<?php foreach ($roles as $k => $lbl) : ?>
<option value="<?php echo $k; ?>" <?php echo $k === 'student' ? 'selected' : ''; ?>><?php echo e($lbl); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-plus me-1"></i> Aggiungi</button>
</div>
</form>
<small class="text-muted d-block mt-2">Se l'utente è già membro, viene aggiornato il ruolo e riattivato.</small>
</div>
<!-- ELENCO MEMBRI -->
<div class="sa-card">
<h5><i class="fas fa-users me-1" style="color:#1ebf73;"></i> Membri</h5>
<div class="count-chips">
<a class="count-chip <?php echo $fRole === '' ? 'active' : ''; ?>" href="<?php echo $back; ?>">Tutti (<?php echo array_sum($counts); ?>)</a>
<?php foreach ($roles as $k => $lbl) : ?>
<a class="count-chip <?php echo $fRole === $k ? 'active' : ''; ?>" href="<?php echo $back; ?>&role=<?php echo $k; ?>"><?php echo e($lbl); ?> (<?php echo (int) ($counts[$k] ?? 0); ?>)</a>
<?php endforeach; ?>
</div>
<form method="get" action="sa-school-members.php" class="row g-2 mb-3">
<input type="hidden" name="id" value="<?php echo $sid; ?>">
<input type="hidden" name="role" value="<?php echo e($fRole); ?>">
<div class="col-md-6">
<input type="text" name="q" class="form-control" placeholder="Cerca per nome, cognome o email" value="<?php echo e($fQ); ?>">
</div>
<div class="col-md-2">
<button class="btn btn-light w-100"><i class="fas fa-search"></i> Cerca</button>
</div>
</form>
<?php if (empty($members)) : ?>
<p class="text-center text-muted py-3">Nessun membro trovato.</p>
<?php endif; ?>
<?php foreach ($members as $mb) :
$idsu = (int) $mb['idschooluser'];
$full = trim($mb['first_name'] . ' ' . $mb['last_name']);
$ini = mb_substr($mb['first_name'] ?? '', 0, 1) . mb_substr($mb['last_name'] ?? '', 0, 1);
$rc = $roleColors[$mb['school_role']] ?? ['#eef2f7', '#374151'];
$active = $mb['active'] === 'Y';
?>
<div class="member-row <?php echo $active ? '' : 'inactive'; ?>">
<div class="avatar"><?php echo e($ini ?: '?'); ?></div>
<div class="member-info">
<span class="member-name"><?php echo e($full ?: '(senza nome)'); ?></span>
<span class="role-pill" style="background:<?php echo $rc[0]; ?>;color:<?php echo $rc[1]; ?>;"><?php echo e($roles[$mb['school_role']] ?? $mb['school_role']); ?></span>
<?php if (!$active) : ?><span class="role-pill" style="background:#1f2937;color:#fff;">Disattivato</span><?php endif; ?>
<div class="member-email"><?php echo e($mb['email']); ?></div>
</div>
<div class="member-actions">
<form method="post" action="sa-school-members.php" class="m-0">
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
<input type="hidden" name="action" value="role">
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
<input type="hidden" name="idschooluser" value="<?php echo $idsu; ?>">
<select name="role" class="form-select" onchange="this.form.submit()">
<?php foreach ($roles as $k => $lbl) : ?>
<option value="<?php echo $k; ?>" <?php echo $k === $mb['school_role'] ? 'selected' : ''; ?>><?php echo e($lbl); ?></option>
<?php endforeach; ?>
</select>
</form>
<form method="post" action="sa-school-members.php" class="m-0">
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
<input type="hidden" name="idschooluser" value="<?php echo $idsu; ?>">
<button type="submit" class="btn-soft toggle" title="<?php echo $active ? 'Disattiva' : 'Riattiva'; ?>">
<i class="fas <?php echo $active ? 'fa-user-slash' : 'fa-user-check'; ?>"></i>
</button>
</form>
<form method="post" action="sa-school-members.php" class="m-0 form-remove" data-name="<?php echo e($full); ?>">
<input type="hidden" name="token" value="<?php echo e($csrf); ?>">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="idschool" value="<?php echo $sid; ?>">
<input type="hidden" name="idschooluser" value="<?php echo $idsu; ?>">
<button type="submit" class="btn-soft remove" title="Rimuovi dalla scuola"><i class="fas fa-trash"></i></button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php if (count($members) === 300) : ?>
<p class="text-muted small mt-2">Mostrati i primi 300 risultati: usa la ricerca per restringere.</p>
<?php endif; ?>
</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-remove', function(e) {
e.preventDefault();
const form = this;
Swal.fire({
title: 'Rimuovere dalla scuola?',
html: '<strong>' + $('<div>').text($(form).data('name')).html() + '</strong><br><br>L\'account resta attivo sul portale, ma perde l\'accesso a questa scuola.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#e74c3c',
confirmButtonText: 'Sì, rimuovi',
cancelButtonText: 'Annulla'
}).then((r) => {
if (r.isConfirmed) form.submit();
});
});
</script>
</body>
</html>
+494
View File
@@ -0,0 +1,494 @@
<?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>
+1 -2
View File
@@ -26,6 +26,5 @@ if (!SchoolContext::switchTo($target)) {
die('Non hai accesso a questa scuola.');
}
$landing = SchoolContext::isStaff() ? 'adminpanel.php' : 'userpanel.php';
header('Location: ' . $landing);
header('Location: ' . SchoolContext::landingPage());
exit;
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* Cambio vista (superadmin / scuola / insegnante / allievo).
* POST: mode, token. Consente solo viste permesse dal ruolo reale.
*/
require_once('include/headscript.php');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . SchoolContext::landingPage());
exit;
}
$token = $_POST['token'] ?? '';
if (empty($_SESSION['csrf_switch']) || !hash_equals($_SESSION['csrf_switch'], $token)) {
http_response_code(400);
die('Richiesta non valida.');
}
if (!SchoolContext::setViewMode((string) ($_POST['mode'] ?? ''))) {
http_response_code(403);
die('Vista non consentita.');
}
header('Location: ' . SchoolContext::landingPage());
exit;