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
include('include/headscript.php');
$conn = new mysqli($servername, $username, $password, $dbname);
require_once('include/headscript.php');
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'])) {
$iduser = $_POST['iduser'];
$datebirth = $_POST['datebirth'];
$yogafor = $_POST['yogafor'];
$healthissue = $_POST['healthissue'];
$generalcomment = $_POST['generalcomment'];
$kind = $_POST['kind'];
$pdo = DBHandlerSelect::getInstance()->getConnection();
$iduser = (int) $iduserlogin;
if ($kind=='insert') {
// Inserimento dei dati nel database
$sql = "INSERT INTO userprofile (iduser, datebirth, yogafor, healthissue, generalcomment)
VALUES ('$iduser', '$datebirth', '$yogafor', '$healthissue', '$generalcomment')
ON DUPLICATE KEY UPDATE
datebirth = '$datebirth', yogafor = '$yogafor', healthissue = '$healthissue', generalcomment = '$generalcomment'";
$datebirth = trim((string) ($_POST['datebirth'] ?? ''));
if ($datebirth !== '' && !DateTime::createFromFormat('Y-m-d', $datebirth)) {
header('Location: userprofile.php?message=baddate');
exit;
}
if ($conn->query($sql) === TRUE) {
echo "Dati inseriti/aggiornati con successo.";
$data = [
':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 {
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");
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();
header('Location: userprofile.php?message=success');
} catch (Throwable $ex) {
error_log('process.php (user ' . $iduser . '): ' . $ex->getMessage());
header('Location: userprofile.php?message=error');
}
}
$conn->close();
?>
exit;
+35 -127
View File
@@ -2,7 +2,8 @@
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();
$iduserlogin = (int) $iduserlogin;
@@ -12,101 +13,28 @@ function e($value): string
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
$message = isset($_GET['message']) ? $_GET['message'] : '';
$uploadFeedback = null; // ['type' => 'success'|'error', 'text' => '...']
$message = $_GET['message'] ?? '';
/* -------------------------------------------------------------------------
* Gestione upload certificato (POST) — con validazione robusta
* ---------------------------------------------------------------------- */
$allowedExt = ['pdf', 'jpg', 'jpeg', 'png'];
$allowedMime = ['application/pdf', 'image/jpeg', 'image/png'];
$maxBytes = 8 * 1024 * 1024; // 8 MB
$uploadDir = 'user/document/';
$stmtProfile = $pdo->prepare("SELECT * FROM userprofile WHERE iduser = ? AND idschool = ? LIMIT 1");
$stmtProfile->execute([$iduserlogin, $idschool]);
$rowdata = $stmtProfile->fetch() ?: [];
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['fileToUpload'])
&& $_FILES['fileToUpload']['error'] === UPLOAD_ERR_OK
) {
$datebirthFromDatabase = $rowdata['datebirth'] ?? '';
$yogaforFromDatabase = $rowdata['yogafor'] ?? '';
$healthissueFromDatabase = $rowdata['healthissue'] ?? '';
$generalcommentFromDatabase = $rowdata['generalcomment'] ?? '';
$isUpdate = !empty($rowdata);
$file = $_FILES['fileToUpload'];
$documentDescription = trim($_POST['documentDescription'] ?? '');
$expiryDate = trim($_POST['expiryDate'] ?? '');
// Numero certificati validi (per il riquadro di collegamento)
$stmtCert = $pdo->prepare(
"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');
$uploadFeedback = null;
?>
<!doctype html>
<html lang="it">
@@ -309,9 +237,11 @@ $currentYear = date('Y');
<div class="profile-wrap">
<?php if ($message === 'success') : ?>
<div class="alert alert-success" role="alert">
Profilo aggiornato con successo.
</div>
<div class="alert alert-success" role="alert">Profilo aggiornato con successo.</div>
<?php elseif ($message === 'baddate') : ?>
<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 if ($uploadFeedback) : ?>
@@ -327,8 +257,6 @@ $currentYear = date('Y');
<p class="section-lead">Compila o aggiorna le informazioni del tuo profilo.</p>
<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">
<label for="datebirth">Data di nascita</label>
@@ -358,38 +286,18 @@ $currentYear = date('Y');
</div>
</div>
<!-- Upload certificato -->
<!-- Certificati: collegamento alla pagina dedicata -->
<div class="card profile-card">
<div class="card-body">
<div class="section-title">Certificati e documenti</div>
<p class="section-lead">Carica il tuo certificato medico o altri documenti (PDF, JPG o PNG, max 8 MB).</p>
<form action="" method="post" enctype="multipart/form-data">
<div class="form-field">
<label for="documentDescription">Descrizione documento</label>
<input type="text" id="documentDescription" class="form-control"
name="documentDescription" placeholder="Es. Certificato medico 2026" required>
</div>
<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 class="card-body d-flex align-items-center justify-content-between flex-wrap gap-3">
<div>
<div class="section-title">Certificato medico</div>
<?php if ($validCertificates > 0) : ?>
<p class="mb-0" style="color:#1a7f52;"><i class="fas fa-circle-check me-1"></i> Hai un certificato valido caricato.</p>
<?php else : ?>
<p class="mb-0" style="color:#c0392b;"><i class="fas fa-triangle-exclamation me-1"></i> Nessun certificato valido: caricalo per praticare.</p>
<?php endif; ?>
</div>
<a href="userprofiledoc.php" class="btn btn-primary btn-save"><i class="fas fa-file-medical me-1"></i> Gestisci certificati</a>
</div>
</div>
+30 -40
View File
@@ -2,7 +2,8 @@
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();
$iduserlogin = (int) $iduserlogin;
@@ -12,57 +13,45 @@ function e($value): string
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'];
$allowedMime = ['application/pdf', 'image/jpeg', 'image/png'];
$maxBytes = 16 * 1024 * 1024; // 16 MB
$uploadDir = 'user/document/';
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_FILES['fileToUpload'])
&& $_FILES['fileToUpload']['error'] === UPLOAD_ERR_OK
) {
// 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'])) {
$file = $_FILES['fileToUpload'];
$documentDescription = trim($_POST['documentDescription'] ?? '');
$expiryDate = trim($_POST['expiryDate'] ?? '');
if ($documentDescription === '' || $expiryDate === '') {
$uploadFeedback = ['type' => 'error', 'text' => 'Descrizione e data di scadenza sono obbligatorie.'];
if ($file['error'] !== UPLOAD_ERR_OK) {
$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) {
$uploadFeedback = ['type' => 'error', 'text' => 'Il file supera la dimensione massima di 16 MB.'];
} else {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMime = $finfo->file($file['tmp_name']);
$realMime = (new finfo(FILEINFO_MIME_TYPE))->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 {
$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)) {
$sql = "INSERT INTO certificateuserprofile
(iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at)
VALUES (:iduser, :descr, :fname, :expiry, :uploaded)";
$stmt = $pdo->prepare($sql);
$ok = $stmt->execute([
':iduser' => $iduserlogin,
':descr' => $documentDescription,
':fname' => $safeName,
':expiry' => $expiryDate,
':uploaded' => date('Y-m-d'),
]);
if (move_uploaded_file($file['tmp_name'], $uploadDir . $safeName)) {
$ok = $pdo->prepare(
"INSERT INTO certificateuserprofile
(idschool, iduser, documentdescription, filenamedocument, expirydatedocument, uploaded_at)
VALUES (?, ?, ?, ?, ?, ?)"
)->execute([$idschool, $iduserlogin, $documentDescription, $safeName, $expiryDate, date('Y-m-d')]);
$uploadFeedback = $ok
? ['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->execute([':iduser' => $iduserlogin]);
$stmtDocs = $pdo->prepare(
"SELECT * FROM certificateuserprofile WHERE iduser = ? AND idschool = ? ORDER BY uploaded_at DESC, idcertificateuserprofile DESC"
);
$stmtDocs->execute([$iduserlogin, $idschool]);
$documents = $stmtDocs->fetchAll();
?>
<!doctype html>
@@ -374,9 +359,14 @@ $documents = $stmtDocs->fetchAll();
<?php foreach ($documents as $document) : ?>
<tr>
<td><?php echo e($document['documentdescription']); ?></td>
<td><?php echo e($document['expirydatedocument']); ?></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
</a>
</td>