Profilo e certificati per scuola, process.php sicuro, apertura certificati protetta

This commit is contained in:
2026-10-05 08:58:16 +02:00
parent 96986d7adb
commit f72f330bc7
4 changed files with 160 additions and 214 deletions
+52
View File
@@ -0,0 +1,52 @@
<?php
require_once('include/headscript.php');
/**
* Visualizza/scarica un certificato in modo protetto.
* GET: id = idcertificateuserprofile.
* Consentito a: proprietaria del certificato, staff della scuola del certificato.
*/
$pdo = DBHandlerSelect::getInstance()->getConnection();
$id = (int) ($_GET['id'] ?? 0);
$st = $pdo->prepare("SELECT * FROM certificateuserprofile WHERE idcertificateuserprofile = ? AND idschool = ?");
$st->execute([$id, $idschool]);
$cert = $st->fetch();
$allowed = $cert && ((int) $cert['iduser'] === (int) $iduserlogin || SchoolContext::isStaff());
if (!$allowed) {
http_response_code(404);
exit('Documento non trovato.');
}
// Percorsi possibili: archivio privato (futuro) e cartella storica pubblica
$name = basename((string) $cert['filenamedocument']);
$candidates = [
dirname(__DIR__) . '/storage/app/certificates/' . (int) $cert['idschool'] . '/' . $name,
__DIR__ . '/user/document/' . $name,
];
$path = null;
foreach ($candidates as $c) {
if (is_file($c)) {
$path = $c;
break;
}
}
if (!$path) {
http_response_code(404);
exit('File non disponibile.');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($path);
if (!in_array($mime, ['application/pdf', 'image/jpeg', 'image/png'], true)) {
http_response_code(415);
exit('Formato non supportato.');
}
header('Content-Type: ' . $mime);
header('Content-Length: ' . filesize($path));
header('Content-Disposition: inline; filename="certificato-' . $id . '.' . pathinfo($name, PATHINFO_EXTENSION) . '"');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: private, no-store');
readfile($path);
exit;
+43 -47
View File
@@ -1,56 +1,52 @@
<?php <?php
include('include/headscript.php'); require_once('include/headscript.php');
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) { /**
die("Connessione fallita: " . $conn->connect_error); * Salvataggio profilo allievo (userprofile) per la scuola corrente.
* L'utente è SEMPRE quello loggato (nessun iduser dal form).
*/
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: userprofile.php');
exit;
} }
if (isset($_POST['submit'])) { $pdo = DBHandlerSelect::getInstance()->getConnection();
$iduser = $_POST['iduser']; $iduser = (int) $iduserlogin;
$datebirth = $_POST['datebirth'];
$yogafor = $_POST['yogafor'];
$healthissue = $_POST['healthissue'];
$generalcomment = $_POST['generalcomment'];
$kind = $_POST['kind'];
if ($kind=='insert') { $datebirth = trim((string) ($_POST['datebirth'] ?? ''));
// Inserimento dei dati nel database if ($datebirth !== '' && !DateTime::createFromFormat('Y-m-d', $datebirth)) {
$sql = "INSERT INTO userprofile (iduser, datebirth, yogafor, healthissue, generalcomment) header('Location: userprofile.php?message=baddate');
VALUES ('$iduser', '$datebirth', '$yogafor', '$healthissue', '$generalcomment') exit;
ON DUPLICATE KEY UPDATE }
datebirth = '$datebirth', yogafor = '$yogafor', healthissue = '$healthissue', generalcomment = '$generalcomment'";
if ($conn->query($sql) === TRUE) { $data = [
echo "Dati inseriti/aggiornati con successo."; ':datebirth' => $datebirth !== '' ? $datebirth : null,
':yogafor' => trim((string) ($_POST['yogafor'] ?? '')),
':healthissue' => trim((string) ($_POST['healthissue'] ?? '')),
':generalcomment' => trim((string) ($_POST['generalcomment'] ?? '')),
':iduser' => $iduser,
':idschool' => $idschool,
];
try {
$st = $pdo->prepare("SELECT iduserprofile FROM userprofile WHERE iduser = ? AND idschool = ? LIMIT 1");
$st->execute([$iduser, $idschool]);
if ($st->fetchColumn()) {
$pdo->prepare(
"UPDATE userprofile
SET datebirth = :datebirth, yogafor = :yogafor, healthissue = :healthissue, generalcomment = :generalcomment
WHERE iduser = :iduser AND idschool = :idschool"
)->execute($data);
} else { } else {
echo "Errore nell'esecuzione della query: " . $conn->error; $pdo->prepare(
"INSERT INTO userprofile (iduser, idschool, datebirth, yogafor, healthissue, generalcomment)
VALUES (:iduser, :idschool, :datebirth, :yogafor, :healthissue, :generalcomment)"
)->execute($data);
} }
header('Location: userprofile.php?message=success');
} catch (Throwable $ex) {
error_log('process.php (user ' . $iduser . '): ' . $ex->getMessage());
header('Location: userprofile.php?message=error');
header("Location: userprofile.php?message=success");
exit();
} else {
// Update dei dati nel database
$sql = "UPDATE userprofile SET datebirth = '$datebirth', yogafor = '$yogafor', healthissue = '$healthissue', generalcomment = '$generalcomment'
WHERE iduser='$iduser'";
if ($conn->query($sql) === TRUE) {
echo "Dati inseriti/aggiornati con successo.";
} else {
echo "Errore nell'esecuzione della query: " . $conn->error;
}
header("Location: userprofile.php?message=success");
exit();
} }
exit;
}
$conn->close();
?>
+35 -127
View File
@@ -2,7 +2,8 @@
require_once('include/headscript.php'); require_once('include/headscript.php');
/** /**
* Connessione unica PDO (singleton). $iduserlogin dalla sessione autenticata. * Profilo allievo per la scuola corrente (dati personali e di salute).
* Il salvataggio avviene in process.php. I certificati si gestiscono in userprofiledoc.php.
*/ */
$pdo = DBHandlerSelect::getInstance()->getConnection(); $pdo = DBHandlerSelect::getInstance()->getConnection();
$iduserlogin = (int) $iduserlogin; $iduserlogin = (int) $iduserlogin;
@@ -12,101 +13,28 @@ function e($value): string
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
} }
$message = isset($_GET['message']) ? $_GET['message'] : ''; $message = $_GET['message'] ?? '';
$uploadFeedback = null; // ['type' => 'success'|'error', 'text' => '...']
/* ------------------------------------------------------------------------- $stmtProfile = $pdo->prepare("SELECT * FROM userprofile WHERE iduser = ? AND idschool = ? LIMIT 1");
* Gestione upload certificato (POST) — con validazione robusta $stmtProfile->execute([$iduserlogin, $idschool]);
* ---------------------------------------------------------------------- */ $rowdata = $stmtProfile->fetch() ?: [];
$allowedExt = ['pdf', 'jpg', 'jpeg', 'png'];
$allowedMime = ['application/pdf', 'image/jpeg', 'image/png'];
$maxBytes = 8 * 1024 * 1024; // 8 MB
$uploadDir = 'user/document/';
if ( $datebirthFromDatabase = $rowdata['datebirth'] ?? '';
$_SERVER['REQUEST_METHOD'] === 'POST' $yogaforFromDatabase = $rowdata['yogafor'] ?? '';
&& isset($_FILES['fileToUpload']) $healthissueFromDatabase = $rowdata['healthissue'] ?? '';
&& $_FILES['fileToUpload']['error'] === UPLOAD_ERR_OK $generalcommentFromDatabase = $rowdata['generalcomment'] ?? '';
) { $isUpdate = !empty($rowdata);
$file = $_FILES['fileToUpload']; // Numero certificati validi (per il riquadro di collegamento)
$documentDescription = trim($_POST['documentDescription'] ?? ''); $stmtCert = $pdo->prepare(
$expiryDate = trim($_POST['expiryDate'] ?? ''); "SELECT COUNT(*) FROM certificateuserprofile
WHERE iduser = ? AND idschool = ? AND (expirydatedocument IS NULL OR expirydatedocument >= CURDATE())"
);
$stmtCert->execute([$iduserlogin, $idschool]);
$validCertificates = (int) $stmtCert->fetchColumn();
// 1) Dimensione
if ($file['size'] > $maxBytes) {
$uploadFeedback = ['type' => 'error', 'text' => 'Il file supera la dimensione massima di 8 MB.'];
} else {
// 2) Estensione
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
// 3) Tipo MIME reale (non ci fidiamo dell'estensione dichiarata)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMime = $finfo->file($file['tmp_name']);
if (!in_array($ext, $allowedExt, true) || !in_array($realMime, $allowedMime, true)) {
$uploadFeedback = ['type' => 'error', 'text' => 'Formato non consentito. Carica un PDF, JPG o PNG.'];
} else {
// 4) Nome file sicuro generato dal server (niente input utente nel percorso)
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destination = $uploadDir . $safeName;
if (!is_dir($uploadDir)) {
@mkdir($uploadDir, 0755, true);
}
if (move_uploaded_file($file['tmp_name'], $destination)) {
// 5) INSERT con prepared statement
$sql = "INSERT INTO certificateuserprofile
(iduser, documentdescription, filenamedocument, expirydatedocument)
VALUES (:iduser, :descr, :fname, :expiry)";
$stmt = $pdo->prepare($sql);
$ok = $stmt->execute([
':iduser' => $iduserlogin,
':descr' => $documentDescription,
':fname' => $safeName,
':expiry' => $expiryDate !== '' ? $expiryDate : null,
]);
$uploadFeedback = $ok
? ['type' => 'success', 'text' => 'Documento caricato correttamente.']
: ['type' => 'error', 'text' => 'Errore nel salvataggio del documento. Riprova.'];
} else {
$uploadFeedback = ['type' => 'error', 'text' => 'Caricamento del file non riuscito. Riprova.'];
}
}
}
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['fileToUpload'])
&& $_FILES['fileToUpload']['error'] !== UPLOAD_ERR_NO_FILE
) {
// Un file è stato scelto ma l'upload è fallito lato PHP
$uploadFeedback = ['type' => 'error', 'text' => 'Si è verificato un problema durante il caricamento. Riprova.'];
}
/* -------------------------------------------------------------------------
* Dati profilo utente
* ---------------------------------------------------------------------- */
$idprofile = null;
$datebirthFromDatabase = '';
$yogaforFromDatabase = '';
$healthissueFromDatabase = '';
$generalcommentFromDatabase = '';
$stmtProfile = $pdo->prepare("SELECT * FROM userprofile WHERE iduser = :iduser LIMIT 1");
$stmtProfile->execute([':iduser' => $iduserlogin]);
$rowdata = $stmtProfile->fetch();
if ($rowdata) {
$idprofile = $rowdata['iduserprofile'];
$datebirthFromDatabase = $rowdata['datebirth'] ?? '';
$yogaforFromDatabase = $rowdata['yogafor'] ?? '';
$healthissueFromDatabase = $rowdata['healthissue'] ?? '';
$generalcommentFromDatabase = $rowdata['generalcomment'] ?? '';
}
$isUpdate = !empty($idprofile);
$currentYear = date('Y'); $currentYear = date('Y');
$uploadFeedback = null;
?> ?>
<!doctype html> <!doctype html>
<html lang="it"> <html lang="it">
@@ -309,9 +237,11 @@ $currentYear = date('Y');
<div class="profile-wrap"> <div class="profile-wrap">
<?php if ($message === 'success') : ?> <?php if ($message === 'success') : ?>
<div class="alert alert-success" role="alert"> <div class="alert alert-success" role="alert">Profilo aggiornato con successo.</div>
Profilo aggiornato con successo. <?php elseif ($message === 'baddate') : ?>
</div> <div class="alert alert-danger" role="alert">Data di nascita non valida (formato AAAA-MM-GG).</div>
<?php elseif ($message === 'error') : ?>
<div class="alert alert-danger" role="alert">Errore durante il salvataggio. Riprova.</div>
<?php endif; ?> <?php endif; ?>
<?php if ($uploadFeedback) : ?> <?php if ($uploadFeedback) : ?>
@@ -327,8 +257,6 @@ $currentYear = date('Y');
<p class="section-lead">Compila o aggiorna le informazioni del tuo profilo.</p> <p class="section-lead">Compila o aggiorna le informazioni del tuo profilo.</p>
<form action="process.php" method="post"> <form action="process.php" method="post">
<input type="hidden" name="iduser" value="<?php echo e($iduserlogin); ?>">
<input type="hidden" name="kind" value="<?php echo $isUpdate ? 'update' : 'insert'; ?>">
<div class="form-field"> <div class="form-field">
<label for="datebirth">Data di nascita</label> <label for="datebirth">Data di nascita</label>
@@ -358,38 +286,18 @@ $currentYear = date('Y');
</div> </div>
</div> </div>
<!-- Upload certificato --> <!-- Certificati: collegamento alla pagina dedicata -->
<div class="card profile-card"> <div class="card profile-card">
<div class="card-body"> <div class="card-body d-flex align-items-center justify-content-between flex-wrap gap-3">
<div class="section-title">Certificati e documenti</div> <div>
<p class="section-lead">Carica il tuo certificato medico o altri documenti (PDF, JPG o PNG, max 8 MB).</p> <div class="section-title">Certificato medico</div>
<?php if ($validCertificates > 0) : ?>
<form action="" method="post" enctype="multipart/form-data"> <p class="mb-0" style="color:#1a7f52;"><i class="fas fa-circle-check me-1"></i> Hai un certificato valido caricato.</p>
<div class="form-field"> <?php else : ?>
<label for="documentDescription">Descrizione documento</label> <p class="mb-0" style="color:#c0392b;"><i class="fas fa-triangle-exclamation me-1"></i> Nessun certificato valido: caricalo per praticare.</p>
<input type="text" id="documentDescription" class="form-control" <?php endif; ?>
name="documentDescription" placeholder="Es. Certificato medico 2026" required> </div>
</div> <a href="userprofiledoc.php" class="btn btn-primary btn-save"><i class="fas fa-file-medical me-1"></i> Gestisci certificati</a>
<div class="form-field">
<label for="expiryDate">Data di scadenza</label>
<input type="text" id="expiryDate" class="form-control" name="expiryDate" placeholder="AAAA-MM-GG">
<div class="hint">Lascia vuoto se il documento non ha scadenza.</div>
</div>
<div class="form-field">
<label>File</label>
<label class="upload-drop d-block" for="fileToUpload" style="cursor:pointer;">
<div class="upload-icon"><i class="fas fa-cloud-arrow-up"></i></div>
<div>Clicca per selezionare un file</div>
<span class="file-chosen" id="fileChosenLabel">Nessun file selezionato</span>
</label>
<input type="file" id="fileToUpload" name="fileToUpload" class="d-none"
accept=".pdf,.jpg,.jpeg,.png" onchange="handleFileChange(this)" required>
</div>
<button type="submit" class="btn btn-primary btn-save">Carica documento</button>
</form>
</div> </div>
</div> </div>
+30 -40
View File
@@ -2,7 +2,8 @@
require_once('include/headscript.php'); require_once('include/headscript.php');
/** /**
* Connessione unica PDO (singleton). $iduserlogin dalla sessione autenticata. * Certificati medici dell'allievo per la scuola corrente: elenco e caricamento.
* I file si aprono tramite certificate.php (controllo permessi).
*/ */
$pdo = DBHandlerSelect::getInstance()->getConnection(); $pdo = DBHandlerSelect::getInstance()->getConnection();
$iduserlogin = (int) $iduserlogin; $iduserlogin = (int) $iduserlogin;
@@ -12,57 +13,45 @@ function e($value): string
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
} }
$uploadFeedback = null; // ['type' => 'success'|'error', 'text' => '...'] $uploadFeedback = null;
/* ------------------------------------------------------------------------- /* -------------------------------------------------------------------------
* Upload certificato (POST) con validazione robusta * Upload certificato (POST)
* ---------------------------------------------------------------------- */ * ---------------------------------------------------------------------- */
$allowedExt = ['pdf', 'jpg', 'jpeg', 'png']; $allowedExt = ['pdf', 'jpg', 'jpeg', 'png'];
$allowedMime = ['application/pdf', 'image/jpeg', 'image/png']; $allowedMime = ['application/pdf', 'image/jpeg', 'image/png'];
$maxBytes = 16 * 1024 * 1024; // 16 MB $maxBytes = 16 * 1024 * 1024; // 16 MB
$uploadDir = 'user/document/'; // Cartella storica: verrà spostata fuori da public insieme a certificati.php e alle API
$uploadDir = __DIR__ . '/user/document/';
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['fileToUpload'])
&& $_FILES['fileToUpload']['error'] === UPLOAD_ERR_OK
) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['fileToUpload'])) {
$file = $_FILES['fileToUpload']; $file = $_FILES['fileToUpload'];
$documentDescription = trim($_POST['documentDescription'] ?? ''); $documentDescription = trim($_POST['documentDescription'] ?? '');
$expiryDate = trim($_POST['expiryDate'] ?? ''); $expiryDate = trim($_POST['expiryDate'] ?? '');
if ($documentDescription === '' || $expiryDate === '') { if ($file['error'] !== UPLOAD_ERR_OK) {
$uploadFeedback = ['type' => 'error', 'text' => 'Descrizione e data di scadenza sono obbligatorie.']; $uploadFeedback = ['type' => 'error', 'text' => 'Si è verificato un problema durante il caricamento. Riprova.'];
} elseif ($documentDescription === '' || !DateTime::createFromFormat('Y-m-d', $expiryDate)) {
$uploadFeedback = ['type' => 'error', 'text' => 'Descrizione e data di scadenza valida sono obbligatorie.'];
} elseif ($file['size'] > $maxBytes) { } elseif ($file['size'] > $maxBytes) {
$uploadFeedback = ['type' => 'error', 'text' => 'Il file supera la dimensione massima di 16 MB.']; $uploadFeedback = ['type' => 'error', 'text' => 'Il file supera la dimensione massima di 16 MB.'];
} else { } else {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$finfo = new finfo(FILEINFO_MIME_TYPE); $realMime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
$realMime = $finfo->file($file['tmp_name']);
if (!in_array($ext, $allowedExt, true) || !in_array($realMime, $allowedMime, true)) { if (!in_array($ext, $allowedExt, true) || !in_array($realMime, $allowedMime, true)) {
$uploadFeedback = ['type' => 'error', 'text' => 'Formato non consentito. Carica un PDF, JPG o PNG.']; $uploadFeedback = ['type' => 'error', 'text' => 'Formato non consentito. Carica un PDF, JPG o PNG.'];
} else { } else {
$safeName = bin2hex(random_bytes(16)) . '.' . $ext; $safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destination = $uploadDir . $safeName;
if (!is_dir($uploadDir)) { if (!is_dir($uploadDir)) {
@mkdir($uploadDir, 0755, true); @mkdir($uploadDir, 0755, true);
} }
if (move_uploaded_file($file['tmp_name'], $uploadDir . $safeName)) {
if (move_uploaded_file($file['tmp_name'], $destination)) { $ok = $pdo->prepare(
$sql = "INSERT INTO certificateuserprofile "INSERT INTO certificateuserprofile
(iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at) (idschool, iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at)
VALUES (:iduser, :descr, :fname, :expiry, :uploaded)"; VALUES (?, ?, ?, ?, ?, ?)"
$stmt = $pdo->prepare($sql); )->execute([$idschool, $iduserlogin, $documentDescription, $safeName, $expiryDate, date('Y-m-d')]);
$ok = $stmt->execute([
':iduser' => $iduserlogin,
':descr' => $documentDescription,
':fname' => $safeName,
':expiry' => $expiryDate,
':uploaded' => date('Y-m-d'),
]);
$uploadFeedback = $ok $uploadFeedback = $ok
? ['type' => 'success', 'text' => 'Documento caricato correttamente.'] ? ['type' => 'success', 'text' => 'Documento caricato correttamente.']
@@ -72,19 +61,15 @@ if (
} }
} }
} }
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['fileToUpload'])
&& $_FILES['fileToUpload']['error'] !== UPLOAD_ERR_NO_FILE
) {
$uploadFeedback = ['type' => 'error', 'text' => 'Si è verificato un problema durante il caricamento. Riprova.'];
} }
/* ------------------------------------------------------------------------- /* -------------------------------------------------------------------------
* Elenco documenti dell'utente * Elenco documenti dell'utente nella scuola corrente
* ---------------------------------------------------------------------- */ * ---------------------------------------------------------------------- */
$stmtDocs = $pdo->prepare("SELECT * FROM certificateuserprofile WHERE iduser = :iduser ORDER BY uploaded_at DESC"); $stmtDocs = $pdo->prepare(
$stmtDocs->execute([':iduser' => $iduserlogin]); "SELECT * FROM certificateuserprofile WHERE iduser = ? AND idschool = ? ORDER BY uploaded_at DESC, idcertificateuserprofile DESC"
);
$stmtDocs->execute([$iduserlogin, $idschool]);
$documents = $stmtDocs->fetchAll(); $documents = $stmtDocs->fetchAll();
?> ?>
<!doctype html> <!doctype html>
@@ -374,9 +359,14 @@ $documents = $stmtDocs->fetchAll();
<?php foreach ($documents as $document) : ?> <?php foreach ($documents as $document) : ?>
<tr> <tr>
<td><?php echo e($document['documentdescription']); ?></td> <td><?php echo e($document['documentdescription']); ?></td>
<td><?php echo e($document['expirydatedocument']); ?></td>
<td> <td>
<a class="doc-link" href="user/document/<?php echo e(rawurlencode($document['filenamedocument'])); ?>" target="_blank" rel="noopener"> <?php $exp = $document['expirydatedocument'];
$isExp = $exp && $exp < date('Y-m-d'); ?>
<?php echo $exp ? e(date('d-m-Y', strtotime($exp))) : '-'; ?>
<?php if ($isExp) : ?><span class="badge bg-danger ms-1">Scaduto</span><?php endif; ?>
</td>
<td>
<a class="doc-link" href="certificate.php?id=<?php echo (int) $document['idcertificateuserprofile']; ?>" target="_blank" rel="noopener">
<i class="fas fa-file-arrow-down"></i> Apri <i class="fas fa-file-arrow-down"></i> Apri
</a> </a>
</td> </td>