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
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:
/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
Contiene tutti i file statici come CSS, JavaScript e immagini
Logica di backend, connessioni database e script di elaborazione
Interfacce riservate agli amministratori
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:
-- 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
Sistema di Autenticazione Sicuro
1. Connessione al Database
Crea un file sicuro per gestire la connessione al database:
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:
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:
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
Accedi alla Dashboard
Caratteristiche di Sicurezza Implementate
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:
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:
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
Condivisione Documenti Sicura
1. Upload Sicuro dei File
Implementa un sistema di upload che verifica tipo, dimensione e contenuto dei file:
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
2. Visualizzazione Controllata dei Documenti
Crea un sistema che permette ai genitori di vedere solo i documenti dei propri figli:
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:
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 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à
// 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
Checklist di Sicurezza
Sicurezza Database
Autenticazione e Sessioni
Gestione File
Sicurezza Web
Configurazione PHP Sicura
# 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