Guida Completa per Creare una Dashboard per Asilo

Una guida dettagliata per sviluppare un sistema completo di gestione per asili nido con autenticazione sicura, gestione utenti e condivisione documenti.

Sicurezza Avanzata
Gestione Utenti
Condivisione Documenti
Design Responsive
Inizia la Guida

Panoramica del Progetto

Questa guida ti accompagnerà nella creazione di una dashboard completa per la gestione di un asilo nido, utilizzando tecnologie moderne e seguendo le migliori pratiche di sicurezza.

Struttura Tecnica

Frontend

HTML5, CSS3, JavaScript moderno con interfaccia responsive

Backend

PHP con MySQL per gestione dati e logica di business

Architettura

Sistema modulare con autenticazione sicura e permessi differenziati

Funzionalità Principali

Sistema di Autenticazione

  • Login sicuro per genitori e amministratori
  • Gestione sessioni con protezione CSRF
  • Permessi differenziati per ruoli
  • Hash sicuro delle password

Gestione Utenti e Alunni

  • Pannello admin per inserire/modificare alunni
  • Associazione genitori-bambini
  • Profili utente completi
  • Sistema di ruoli e permessi

Condivisione Documenti

  • Upload sicuro e categorizzazione documenti
  • Visualizzazione solo per genitori autorizzati
  • Filtri per tipologia documento
  • Controllo accessi granulare

Prerequisiti

Importante: Prima di iniziare, assicurati di avere un ambiente di sviluppo locale configurato correttamente.

Server Locale

XAMPP, WAMP o MAMP per Apache, MySQL e PHP

Editor di Codice

VS Code, PhpStorm o altro editor con supporto PHP

Database

MySQL 5.7+ o MariaDB con phpMyAdmin

PHP

PHP 7.4+ con estensioni mysqli e session

Struttura del Progetto

Una struttura ben organizzata è fondamentale per la manutenibilità del codice. Ecco la struttura di cartelle consigliata:

Struttura delle Cartelle
/dashboard_asilo/
├── /assets/
│   ├── /css/
│   │   └── style.css
│   ├── /js/
│   │   └── script.js
│   └── /img/
├── /backend/
│   ├── /includes/
│   │   ├── db_connection.php
│   │   └── functions.php
│   ├── /uploads/  # Importante: fuori dalla root web per sicurezza
│   ├── login.php
│   ├── logout.php
│   └── upload_document.php
├── /admin/
│   ├── index.php
│   └── gestione_alunni.php
├── /genitore/
│   ├── index.php
│   └── documenti.php
├── index.php
└── register.php

Spiegazione della Struttura

/assets/

Contiene tutti i file statici come CSS, JavaScript e immagini

/backend/

Logica di backend, connessioni database e script di elaborazione

/admin/

Interfacce riservate agli amministratori

/genitore/

Interfacce per i genitori degli alunni

Database MySQL

Il database è il cuore del sistema. Ecco la struttura delle tabelle principali che puoi eseguire tramite phpMyAdmin:

Schema Database SQL
-- Tabella Utenti (sia admin che genitori)
CREATE TABLE utenti (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    nome VARCHAR(50) NOT NULL,
    cognome VARCHAR(50) NOT NULL,
    ruolo ENUM('admin', 'genitore') NOT NULL,
    data_creazione TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    ultimo_accesso TIMESTAMP NULL,
    attivo BOOLEAN DEFAULT TRUE
);

-- Tabella Alunni
CREATE TABLE alunni (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(50) NOT NULL,
    cognome VARCHAR(50) NOT NULL,
    data_nascita DATE NOT NULL,
    codice_fiscale VARCHAR(16) UNIQUE,
    id_genitore INT,
    note TEXT,
    data_iscrizione TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (id_genitore) REFERENCES utenti(id) ON DELETE SET NULL
);

-- Tabella Documenti
CREATE TABLE documenti (
    id INT AUTO_INCREMENT PRIMARY KEY,
    id_alunno INT NOT NULL,
    nome_file VARCHAR(255) NOT NULL,
    nome_originale VARCHAR(255) NOT NULL,
    percorso_file VARCHAR(255) NOT NULL,
    categoria ENUM('certificato', 'autorizzazione', 'medico', 'altro') DEFAULT 'altro',
    dimensione_file INT,
    tipo_mime VARCHAR(100),
    data_caricamento TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    caricato_da INT,
    FOREIGN KEY (id_alunno) REFERENCES alunni(id) ON DELETE CASCADE,
    FOREIGN KEY (caricato_da) REFERENCES utenti(id) ON DELETE SET NULL
);

-- Tabella Log Accessi (per sicurezza)
CREATE TABLE log_accessi (
    id INT AUTO_INCREMENT PRIMARY KEY,
    id_utente INT,
    ip_address VARCHAR(45),
    user_agent TEXT,
    azione VARCHAR(100),
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (id_utente) REFERENCES utenti(id) ON DELETE SET NULL
);

Caratteristiche del Database

Chiavi Primarie: Ogni tabella ha un ID univoco auto-incrementale
Relazioni: Foreign key per mantenere l'integrità referenziale
Sicurezza: Tabella di log per tracciare gli accessi
Timestamp: Tracciamento automatico di creazione e modifiche

Sistema di Autenticazione Sicuro

Sicurezza Critica: L'autenticazione è la base di tutto il sistema. Non compromettere mai sulla sicurezza delle password e delle sessioni.

1. Connessione al Database

Crea un file sicuro per gestire la connessione al database:

/backend/includes/db_connection.php
set_charset("utf8mb4");
    
    if($conn->connect_error) {
        throw new Exception("Connessione fallita: " . $conn->connect_error);
    }
    
} catch(Exception $e) {
    // Log dell'errore (in produzione, non mostrare dettagli)
    error_log("Errore database: " . $e->getMessage());
    die("Errore di connessione al database. Riprova più tardi.");
}

// Funzione per chiudere la connessione
function closeConnection() {
    global $conn;
    if($conn) {
        $conn->close();
    }
}

// Funzione per log delle attività
function logActivity($user_id, $action, $ip = null) {
    global $conn;
    
    $ip = $ip ?: $_SERVER['REMOTE_ADDR'];
    $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
    
    $stmt = $conn->prepare("INSERT INTO log_accessi (id_utente, ip_address, user_agent, azione) VALUES (?, ?, ?, ?)");
    $stmt->bind_param("isss", $user_id, $ip, $user_agent, $action);
    $stmt->execute();
    $stmt->close();
}
?>

2. Hashing Sicuro delle Password

Mai salvare password in chiaro! Usa sempre l'hashing sicuro:

Esempio di Hashing Password
 false, 'message' => 'Password troppo corta (minimo 8 caratteri)'];
    }
    
    // Hash sicuro della password
    $password_hash = password_hash($password, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536, // 64 MB
        'time_cost' => 4,       // 4 iterazioni
        'threads' => 3          // 3 thread
    ]);
    
    // Verifica se email già esiste
    $check_stmt = $conn->prepare("SELECT id FROM utenti WHERE email = ?");
    $check_stmt->bind_param("s", $email);
    $check_stmt->execute();
    
    if($check_stmt->get_result()->num_rows > 0) {
        return ['success' => false, 'message' => 'Email già registrata'];
    }
    
    // Inserimento nuovo utente
    $stmt = $conn->prepare("INSERT INTO utenti (email, password, nome, cognome, ruolo) VALUES (?, ?, ?, ?, ?)");
    $stmt->bind_param("sssss", $email, $password_hash, $nome, $cognome, $ruolo);
    
    if($stmt->execute()) {
        $user_id = $conn->insert_id;
        logActivity($user_id, 'Registrazione completata');
        return ['success' => true, 'message' => 'Utente registrato con successo'];
    } else {
        return ['success' => false, 'message' => 'Errore durante la registrazione'];
    }
}
?>

3. Sistema di Login Sicuro

Implementa un sistema di login robusto con protezione contro attacchi brute force:

login.php - Sistema Completo
prepare("
        SELECT COUNT(*) as attempts 
        FROM log_accessi 
        WHERE ip_address = ? 
        AND azione = 'Login fallito' 
        AND timestamp > DATE_SUB(NOW(), INTERVAL 15 MINUTE)
    ");
    
    $ip = $_SERVER['REMOTE_ADDR'];
    $stmt->bind_param("s", $ip);
    $stmt->execute();
    $result = $stmt->get_result()->fetch_assoc();
    
    return $result['attempts'] < MAX_LOGIN_ATTEMPTS;
}

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Verifica CSRF token
    if(!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
        die('Token CSRF non valido');
    }
    
    $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
    $password = $_POST['password'];
    $ip = $_SERVER['REMOTE_ADDR'];
    
    // Verifica tentativi di login
    if(!checkLoginAttempts($email)) {
        logActivity(null, 'Troppi tentativi di login', $ip);
        $error = "Troppi tentativi di login. Riprova tra 15 minuti.";
    } else {
        // Query sicura con prepared statement
        $sql = "SELECT id, password, ruolo, nome, cognome, attivo FROM utenti WHERE email = ?";
        
        if ($stmt = $conn->prepare($sql)) {
            $stmt->bind_param("s", $email);
            $stmt->execute();
            $result = $stmt->get_result();

            if ($result->num_rows == 1) {
                $user = $result->fetch_assoc();
                
                // Verifica se account è attivo
                if(!$user['attivo']) {
                    logActivity($user['id'], 'Tentativo accesso account disattivato', $ip);
                    $error = "Account disattivato. Contatta l'amministratore.";
                } else if (password_verify($password, $user['password'])) {
                    // Login riuscito
                    session_regenerate_id(true); // Previene session fixation
                    
                    $_SESSION['loggedin'] = true;
                    $_SESSION['id'] = $user['id'];
                    $_SESSION['nome'] = $user['nome'];
                    $_SESSION['cognome'] = $user['cognome'];
                    $_SESSION['ruolo'] = $user['ruolo'];
                    $_SESSION['login_time'] = time();
                    
                    // Aggiorna ultimo accesso
                    $update_stmt = $conn->prepare("UPDATE utenti SET ultimo_accesso = NOW() WHERE id = ?");
                    $update_stmt->bind_param("i", $user['id']);
                    $update_stmt->execute();
                    
                    logActivity($user['id'], 'Login riuscito', $ip);
                    
                    // Reindirizza in base al ruolo
                    if ($user['ruolo'] == 'admin') {
                        header("location: admin/index.php");
                    } else {
                        header("location: genitore/index.php");
                    }
                    exit;
                } else {
                    logActivity($user['id'], 'Login fallito - password errata', $ip);
                    $error = "Email o password non corretti.";
                }
            } else {
                logActivity(null, 'Login fallito - email non trovata: ' . $email, $ip);
                $error = "Email o password non corretti.";
            }
            $stmt->close();
        }
    }
}
?>




    
    
    Login - Dashboard Asilo
    


    

Caratteristiche di Sicurezza Implementate

Protezione CSRF: Token per prevenire attacchi cross-site
Hash Argon2ID: Algoritmo di hashing più sicuro
Rate Limiting: Protezione contro attacchi brute force
Log Attività: Tracciamento di tutti i tentativi di accesso

Gestione Utenti e Alunni

Il pannello di amministrazione permette la gestione completa di utenti e alunni. Ecco come implementare le funzionalità principali:

1. Protezione delle Pagine Admin

Ogni pagina riservata agli amministratori deve includere questo controllo:

Controllo Accesso Admin
 SESSION_TIMEOUT) {
        session_destroy();
        header('Location: ../index.php?error=session_expired');
        exit;
    }
    
    // Aggiorna timestamp ultima attività
    $_SESSION['last_activity'] = time();
}

// Chiamare questa funzione all'inizio di ogni pagina admin
checkAdminAccess();
?>

2. Gestione Alunni - Interfaccia Completa

Crea un'interfaccia moderna per la gestione degli alunni:

/admin/gestione_alunni.php
prepare("SELECT id FROM alunni WHERE codice_fiscale = ?");
        $check_stmt->bind_param("s", $codice_fiscale);
        $check_stmt->execute();
        if ($check_stmt->get_result()->num_rows > 0) {
            $error = "Codice fiscale già presente nel sistema.";
            return;
        }
    }
    
    $stmt = $conn->prepare("INSERT INTO alunni (nome, cognome, data_nascita, codice_fiscale, id_genitore, note) VALUES (?, ?, ?, ?, ?, ?)");
    $stmt->bind_param("ssssiss", $nome, $cognome, $data_nascita, $codice_fiscale, $id_genitore, $note);
    
    if ($stmt->execute()) {
        $student_id = $conn->insert_id;
        logActivity($_SESSION['id'], "Aggiunto alunno: $nome $cognome (ID: $student_id)");
        $success = "Alunno aggiunto con successo!";
    } else {
        $error = "Errore durante l'inserimento dell'alunno.";
    }
}

// Recupera lista alunni con informazioni genitore
function getStudentsList() {
    global $conn;
    
    $sql = "SELECT a.*, u.nome as nome_genitore, u.cognome as cognome_genitore, u.email as email_genitore 
            FROM alunni a 
            LEFT JOIN utenti u ON a.id_genitore = u.id 
            ORDER BY a.cognome, a.nome";
    
    return $conn->query($sql);
}

// Recupera lista genitori per select
function getParentsList() {
    global $conn;
    
    $sql = "SELECT id, nome, cognome, email FROM utenti WHERE ruolo = 'genitore' ORDER BY cognome, nome";
    return $conn->query($sql);
}

$students = getStudentsList();
$parents = getParentsList();
?>




    
    
    Gestione Alunni - Dashboard Asilo
    


    

Gestione Alunni

fetch_assoc()): ?>
Nome Cognome Data Nascita Genitore Email Genitore Azioni
Non assegnato -

Condivisione Documenti Sicura

Sicurezza Critica: La gestione dei documenti richiede controlli rigorosi per proteggere la privacy dei dati degli alunni.

1. Upload Sicuro dei File

Implementa un sistema di upload che verifica tipo, dimensione e contenuto dei file:

/backend/upload_document.php
 MAX_FILE_SIZE) {
        $errors[] = "File troppo grande. Massimo " . (MAX_FILE_SIZE / 1024 / 1024) . "MB.";
    }
    
    // Verifica tipo MIME
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime_type = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);
    
    if (!in_array($mime_type, ALLOWED_TYPES)) {
        $errors[] = "Tipo di file non consentito. Tipi permessi: PDF, DOC, DOCX, JPG, PNG, GIF.";
    }
    
    // Verifica estensione
    $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowed_extensions = ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png', 'gif'];
    
    if (!in_array($extension, $allowed_extensions)) {
        $errors[] = "Estensione file non consentita.";
    }
    
    return $errors;
}

if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["documento"])) {
    $id_alunno = intval($_POST['id_alunno']);
    $categoria = $_POST['categoria'];
    $file = $_FILES["documento"];
    
    // Validazione input
    if (empty($id_alunno) || empty($categoria)) {
        $response = ['success' => false, 'message' => 'Dati mancanti.'];
    } else {
        // Verifica che l'alunno esista
        $check_stmt = $conn->prepare("SELECT id FROM alunni WHERE id = ?");
        $check_stmt->bind_param("i", $id_alunno);
        $check_stmt->execute();
        
        if ($check_stmt->get_result()->num_rows === 0) {
            $response = ['success' => false, 'message' => 'Alunno non trovato.'];
        } else {
            // Validazione file
            $validation_errors = validateFile($file);
            
            if (!empty($validation_errors)) {
                $response = ['success' => false, 'message' => implode(' ', $validation_errors)];
            } else {
                // Genera nome file sicuro
                $original_name = $file['name'];
                $extension = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
                $safe_filename = uniqid('doc_') . '_' . time() . '.' . $extension;
                $target_path = UPLOAD_DIR . $safe_filename;
                
                // Crea directory se non esiste
                if (!is_dir(UPLOAD_DIR)) {
                    mkdir(UPLOAD_DIR, 0755, true);
                }
                
                // Sposta file
                if (move_uploaded_file($file['tmp_name'], $target_path)) {
                    // Salva informazioni nel database
                    $stmt = $conn->prepare("
                        INSERT INTO documenti (id_alunno, nome_file, nome_originale, percorso_file, categoria, dimensione_file, tipo_mime, caricato_da) 
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                    ");
                    
                    $mime_type = mime_content_type($target_path);
                    $file_size = filesize($target_path);
                    
                    $stmt->bind_param("issssiis", 
                        $id_alunno, 
                        $safe_filename, 
                        $original_name, 
                        $target_path, 
                        $categoria, 
                        $file_size, 
                        $mime_type, 
                        $_SESSION['id']
                    );
                    
                    if ($stmt->execute()) {
                        logActivity($_SESSION['id'], "Caricato documento per alunno ID: $id_alunno");
                        $response = ['success' => true, 'message' => 'Documento caricato con successo.'];
                    } else {
                        // Rimuovi file se inserimento DB fallisce
                        unlink($target_path);
                        $response = ['success' => false, 'message' => 'Errore durante il salvataggio nel database.'];
                    }
                } else {
                    $response = ['success' => false, 'message' => 'Errore durante il caricamento del file.'];
                }
            }
        }
    }
    
    // Risposta JSON per AJAX
    header('Content-Type: application/json');
    echo json_encode($response);
    exit;
}
?>




    
    
    Carica Documento - Dashboard Asilo
    


    

Carica Nuovo Documento

Trascina il file qui o clicca per selezionare

Formati supportati: PDF, DOC, DOCX, JPG, PNG, GIF (max 10MB)

2. Visualizzazione Controllata dei Documenti

Crea un sistema che permette ai genitori di vedere solo i documenti dei propri figli:

/backend/visualizza_documento.php
prepare($sql);
    $stmt->bind_param("i", $id_documento);
} else {
    // Genitore può vedere solo documenti dei propri figli
    $sql = "SELECT d.percorso_file, d.nome_originale, d.tipo_mime, a.nome, a.cognome 
            FROM documenti d 
            JOIN alunni a ON d.id_alunno = a.id 
            WHERE d.id = ? AND a.id_genitore = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("ii", $id_documento, $_SESSION['id']);
}

$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows === 0) {
    http_response_code(403);
    die('Documento non trovato o accesso negato');
}

$documento = $result->fetch_assoc();

// Verifica che il file esista fisicamente
if (!file_exists($documento['percorso_file'])) {
    http_response_code(404);
    die('File non trovato sul server');
}

// Log dell'accesso
logActivity($_SESSION['id'], "Visualizzato documento ID: $id_documento");

// Determina il tipo di visualizzazione
$mime_type = $documento['tipo_mime'];
$filename = $documento['nome_originale'];

// Imposta headers appropriati
header('Content-Type: ' . $mime_type);
header('Content-Length: ' . filesize($documento['percorso_file']));

// Per PDF e immagini, mostra inline; per altri file, forza download
if (strpos($mime_type, 'image/') === 0 || $mime_type === 'application/pdf') {
    header('Content-Disposition: inline; filename="' . $filename . '"');
} else {
    header('Content-Disposition: attachment; filename="' . $filename . '"');
}

// Previeni caching per documenti sensibili
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

// Invia il file
readfile($documento['percorso_file']);
exit;
?>

3. Interfaccia Genitore per Visualizzazione Documenti

Crea una pagina user-friendly per i genitori:

/genitore/documenti.php
prepare($sql);
$stmt->bind_param("i", $_SESSION['id']);
$stmt->execute();
$documenti = $stmt->get_result();

// Raggruppa documenti per alunno
$documenti_per_alunno = [];
while ($doc = $documenti->fetch_assoc()) {
    $alunno_key = $doc['nome_alunno'] . ' ' . $doc['cognome_alunno'];
    $documenti_per_alunno[$alunno_key][] = $doc;
}
?>




    
    
    I Miei Documenti - Dashboard Asilo
    


    

I Miei Documenti

Nessun documento disponibile

Non ci sono ancora documenti caricati per i tuoi bambini.

$docs): ?>

Frontend Moderno e Responsive

Un'interfaccia moderna e responsive è essenziale per l'usabilità del sistema. Ecco le tecnologie e tecniche consigliate:

CSS Moderno

  • Flexbox e Grid: Layout flessibili e responsive
  • CSS Variables: Gestione centralizzata dei colori e dimensioni
  • Media Queries: Adattamento per diversi dispositivi
  • Animazioni CSS: Transizioni fluide e micro-interazioni

JavaScript Interattivo

  • Fetch API: Comunicazione asincrona con il backend
  • Form Validation: Validazione lato client in tempo reale
  • Modal e Dropdown: Interfacce dinamiche
  • File Upload: Upload con progress bar e preview

Design Responsive

  • Mobile First: Progettazione partendo dal mobile
  • Touch Friendly: Elementi facilmente toccabili
  • Progressive Enhancement: Funzionalità base sempre disponibili
  • Accessibilità: Supporto per screen reader e navigazione da tastiera

Esempio di CSS Moderno

CSS Variables e Layout Responsive
/* CSS Variables per consistenza */
:root {
    --primary-color: #3b82f6;
    --secondary-color: #64748b;
    --success-color: #10b981;
    --danger-color: #ef4444;
    --warning-color: #f59e0b;
    
    --bg-primary: #ffffff;
    --bg-secondary: #f8fafc;
    --bg-dark: #1e293b;
    
    --text-primary: #1e293b;
    --text-secondary: #64748b;
    --text-light: #94a3b8;
    
    --border-color: #e2e8f0;
    --border-radius: 8px;
    --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
    
    --font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    --font-size-sm: 0.875rem;
    --font-size-base: 1rem;
    --font-size-lg: 1.125rem;
    --font-size-xl: 1.25rem;
    --font-size-2xl: 1.5rem;
    
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 1.5rem;
    --spacing-xl: 2rem;
    --spacing-2xl: 3rem;
}

/* Reset e base styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    line-height: 1.6;
    color: var(--text-primary);
    background-color: var(--bg-secondary);
}

/* Layout Grid Moderno */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--spacing-md);
}

.grid {
    display: grid;
    gap: var(--spacing-lg);
}

.grid-cols-1 { grid-template-columns: 1fr; }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }

/* Responsive breakpoints */
@media (max-width: 768px) {
    .grid-cols-2,
    .grid-cols-3,
    .grid-cols-4 {
        grid-template-columns: 1fr;
    }
    
    .container {
        padding: 0 var(--spacing-sm);
    }
}

/* Componenti moderni */
.card {
    background: var(--bg-primary);
    border-radius: var(--border-radius);
    box-shadow: var(--shadow);
    padding: var(--spacing-lg);
    transition: all 0.2s ease;
}

.card:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}

.btn {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-sm) var(--spacing-md);
    border: none;
    border-radius: var(--border-radius);
    font-size: var(--font-size-base);
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: all 0.2s ease;
    white-space: nowrap;
}

.btn-primary {
    background-color: var(--primary-color);
    color: white;
}

.btn-primary:hover {
    background-color: #2563eb;
    transform: translateY(-1px);
}

.btn-secondary {
    background-color: var(--secondary-color);
    color: white;
}

.btn-danger {
    background-color: var(--danger-color);
    color: white;
}

/* Form styles moderni */
.form-group {
    margin-bottom: var(--spacing-md);
}

.form-group label {
    display: block;
    margin-bottom: var(--spacing-xs);
    font-weight: 500;
    color: var(--text-primary);
}

.form-control {
    width: 100%;
    padding: var(--spacing-sm) var(--spacing-md);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    font-size: var(--font-size-base);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.form-control:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

/* Animazioni e micro-interazioni */
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes slideIn {
    from { transform: translateX(-100%); }
    to { transform: translateX(0); }
}

.fade-in {
    animation: fadeIn 0.3s ease;
}

.slide-in {
    animation: slideIn 0.3s ease;
}

/* Responsive navigation */
.nav-mobile {
    display: none;
}

@media (max-width: 768px) {
    .nav-desktop {
        display: none;
    }
    
    .nav-mobile {
        display: block;
    }
    
    .mobile-menu {
        position: fixed;
        top: 0;
        left: -100%;
        width: 80%;
        height: 100vh;
        background: var(--bg-primary);
        box-shadow: var(--shadow-lg);
        transition: left 0.3s ease;
        z-index: 1000;
    }
    
    .mobile-menu.active {
        left: 0;
    }
}

JavaScript per Interattività

JavaScript Moderno per Upload e Validazione
// Classe per gestione upload file con progress
class FileUploader {
    constructor(formSelector, options = {}) {
        this.form = document.querySelector(formSelector);
        this.options = {
            maxSize: 10 * 1024 * 1024, // 10MB default
            allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
            progressSelector: '.upload-progress',
            ...options
        };
        
        this.init();
    }
    
    init() {
        if (!this.form) return;
        
        this.form.addEventListener('submit', this.handleSubmit.bind(this));
        
        const fileInput = this.form.querySelector('input[type="file"]');
        if (fileInput) {
            fileInput.addEventListener('change', this.handleFileSelect.bind(this));
        }
    }
    
    handleFileSelect(event) {
        const file = event.target.files[0];
        if (!file) return;
        
        const validation = this.validateFile(file);
        if (!validation.valid) {
            this.showError(validation.message);
            event.target.value = '';
            return;
        }
        
        this.showFilePreview(file);
    }
    
    validateFile(file) {
        if (file.size > this.options.maxSize) {
            return {
                valid: false,
                message: `File troppo grande. Massimo ${this.options.maxSize / 1024 / 1024}MB`
            };
        }
        
        if (!this.options.allowedTypes.includes(file.type)) {
            return {
                valid: false,
                message: 'Tipo di file non supportato'
            };
        }
        
        return { valid: true };
    }
    
    showFilePreview(file) {
        const preview = document.createElement('div');
        preview.className = 'file-preview';
        
        if (file.type.startsWith('image/')) {
            const img = document.createElement('img');
            img.src = URL.createObjectURL(file);
            img.style.maxWidth = '200px';
            img.style.maxHeight = '200px';
            preview.appendChild(img);
        }
        
        const info = document.createElement('div');
        info.innerHTML = `
            

${file.name}

Dimensione: ${(file.size / 1024).toFixed(1)} KB

`; preview.appendChild(info); // Rimuovi preview precedente const existingPreview = this.form.querySelector('.file-preview'); if (existingPreview) { existingPreview.remove(); } this.form.appendChild(preview); } async handleSubmit(event) { event.preventDefault(); const formData = new FormData(this.form); const progressElement = document.querySelector(this.options.progressSelector); try { this.showProgress(true); const response = await fetch(this.form.action, { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { this.showSuccess(result.message); this.form.reset(); this.clearPreview(); } else { this.showError(result.message); } } catch (error) { this.showError('Errore durante il caricamento'); } finally { this.showProgress(false); } } showProgress(show) { const progressElement = document.querySelector(this.options.progressSelector); if (progressElement) { progressElement.style.display = show ? 'block' : 'none'; } } showError(message) { this.showAlert(message, 'error'); } showSuccess(message) { this.showAlert(message, 'success'); } showAlert(message, type) { const alert = document.createElement('div'); alert.className = `alert alert-${type}`; alert.innerHTML = ` ${message} `; // Rimuovi alert precedenti const existingAlerts = document.querySelectorAll('.alert'); existingAlerts.forEach(el => el.remove()); this.form.insertBefore(alert, this.form.firstChild); // Rimuovi automaticamente dopo 5 secondi setTimeout(() => alert.remove(), 5000); } clearPreview() { const preview = this.form.querySelector('.file-preview'); if (preview) { preview.remove(); } } } // Classe per validazione form in tempo reale class FormValidator { constructor(formSelector) { this.form = document.querySelector(formSelector); this.rules = new Map(); this.init(); } init() { if (!this.form) return; // Aggiungi listener per validazione in tempo reale this.form.addEventListener('input', this.handleInput.bind(this)); this.form.addEventListener('blur', this.handleBlur.bind(this), true); this.form.addEventListener('submit', this.handleSubmit.bind(this)); } addRule(fieldName, validator, message) { if (!this.rules.has(fieldName)) { this.rules.set(fieldName, []); } this.rules.get(fieldName).push({ validator, message }); } validateField(field) { const fieldName = field.name; const rules = this.rules.get(fieldName); if (!rules) return { valid: true }; for (const rule of rules) { if (!rule.validator(field.value, field)) { return { valid: false, message: rule.message }; } } return { valid: true }; } handleInput(event) { const field = event.target; if (field.type === 'email' || field.type === 'password') { this.validateAndShowFeedback(field); } } handleBlur(event) { const field = event.target; if (field.tagName === 'INPUT' || field.tagName === 'SELECT' || field.tagName === 'TEXTAREA') { this.validateAndShowFeedback(field); } } validateAndShowFeedback(field) { const result = this.validateField(field); this.showFieldFeedback(field, result); } showFieldFeedback(field, result) { // Rimuovi feedback precedente const existingFeedback = field.parentNode.querySelector('.field-feedback'); if (existingFeedback) { existingFeedback.remove(); } // Rimuovi classi di stato field.classList.remove('field-valid', 'field-invalid'); if (!result.valid) { field.classList.add('field-invalid'); const feedback = document.createElement('div'); feedback.className = 'field-feedback field-feedback-error'; feedback.innerHTML = ` ${result.message}`; field.parentNode.appendChild(feedback); } else if (field.value) { field.classList.add('field-valid'); } } handleSubmit(event) { let isValid = true; // Valida tutti i campi const fields = this.form.querySelectorAll('input, select, textarea'); fields.forEach(field => { const result = this.validateField(field); this.showFieldFeedback(field, result); if (!result.valid) { isValid = false; } }); if (!isValid) { event.preventDefault(); // Scorri al primo errore const firstError = this.form.querySelector('.field-invalid'); if (firstError) { firstError.scrollIntoView({ behavior: 'smooth', block: 'center' }); firstError.focus(); } } } } // Inizializzazione quando il DOM è pronto document.addEventListener('DOMContentLoaded', function() { // Inizializza uploader se presente if (document.querySelector('#uploadForm')) { new FileUploader('#uploadForm'); } // Inizializza validatore per form di login if (document.querySelector('#loginForm')) { const validator = new FormValidator('#loginForm'); validator.addRule('email', value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 'Inserisci un indirizzo email valido' ); validator.addRule('password', value => value.length >= 8, 'La password deve essere di almeno 8 caratteri' ); } // Menu mobile toggle const mobileToggle = document.querySelector('.mobile-menu-toggle'); const mobileMenu = document.querySelector('.mobile-menu'); if (mobileToggle && mobileMenu) { mobileToggle.addEventListener('click', () => { mobileMenu.classList.toggle('active'); }); } // Chiudi menu mobile quando si clicca fuori document.addEventListener('click', (event) => { if (mobileMenu && !mobileMenu.contains(event.target) && !mobileToggle.contains(event.target)) { mobileMenu.classList.remove('active'); } }); });

Considerazioni di Sicurezza

Sicurezza Critica: La sicurezza non è opzionale quando si gestiscono dati sensibili di minori. Implementa sempre tutte le misure di protezione.

Checklist di Sicurezza

Sicurezza Database

Usa sempre prepared statements per prevenire SQL injection
Configura utente database con privilegi minimi necessari
Abilita SSL/TLS per connessioni database
Implementa backup automatici e criptati

Autenticazione e Sessioni

Hash password con algoritmi sicuri (Argon2ID, bcrypt)
Implementa rate limiting per tentativi di login
Usa session_regenerate_id() dopo login
Configura cookie di sessione come HttpOnly e Secure

Gestione File

Valida tipo MIME e estensione file
Limita dimensione massima upload
Salva file fuori dalla web root
Scansiona file per malware (se possibile)

Sicurezza Web

Implementa protezione CSRF con token
Sanitizza e valida tutti gli input utente
Usa HTTPS in produzione
Configura header di sicurezza (CSP, HSTS, etc.)

Configurazione PHP Sicura

php.ini - Configurazioni di Sicurezza
# Nascondi versione PHP
expose_php = Off

# Disabilita funzioni pericolose
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

# Limita upload file
file_uploads = On
upload_max_filesize = 10M
post_max_size = 10M
max_file_uploads = 5

# Configurazione sessioni sicure
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = "Strict"

# Limita memoria e tempo esecuzione
memory_limit = 128M
max_execution_time = 30
max_input_time = 30

# Log errori (non mostrarli in produzione)
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log

Header di Sicurezza

Header di Sicurezza HTTP