Files
zibo-dashboard/public/userarea/user_settings.php
T
2026-05-18 13:31:34 +02:00

868 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php include('include/headscript.php'); ?>
<?php
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$userId = (int)($iduserlogin ?? 0);
if ($userId <= 0) {
die('Utente non valido.');
}
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($_SESSION['user_settings_csrf'])) {
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['user_settings_csrf'];
$successMessage = '';
$errorMessage = '';
// Load countries.
$countries = [];
try {
$stmtCountries = $pdo->query("
SELECT id, name, iso_3166_2
FROM auth_countries
ORDER BY name ASC
");
$countries = $stmtCountries->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$countries = [];
}
// Load current user.
$stmtProfileUser = $pdo->prepare("
SELECT
id,
email,
password,
first_name,
last_name,
phone,
avatar,
address,
country_id,
birthday,
role_id,
status,
last_login
FROM auth_users
WHERE id = ?
LIMIT 1
");
$stmtProfileUser->execute([$userId]);
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
if (!$profileUser) {
die('Utente non trovato.');
}
function e($value)
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
function normalizeAvatarPath($avatar)
{
$avatar = trim((string)$avatar);
if ($avatar === '') {
return '';
}
// If the database already contains a complete relative path, use it as it is.
if (
str_starts_with($avatar, '../') ||
str_starts_with($avatar, './') ||
str_starts_with($avatar, '/') ||
str_starts_with($avatar, 'http://') ||
str_starts_with($avatar, 'https://')
) {
return $avatar;
}
// If the database contains only the filename, build the expected user upload path.
return '../upload/users/' . $avatar;
}
function getAvatarInitials($profileUser)
{
$first = trim((string)($profileUser['first_name'] ?? ''));
$last = trim((string)($profileUser['last_name'] ?? ''));
$email = trim((string)($profileUser['email'] ?? ''));
$initials = '';
if ($first !== '') {
$initials .= mb_substr($first, 0, 1);
}
if ($last !== '') {
$initials .= mb_substr($last, 0, 1);
}
if ($initials === '' && $email !== '') {
$initials = mb_substr($email, 0, 1);
}
return strtoupper($initials ?: 'U');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$postedToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($csrfToken, $postedToken)) {
$errorMessage = 'Sessione non valida. Ricarica la pagina e riprova.';
} else {
$email = trim($_POST['email'] ?? '');
$firstName = trim($_POST['first_name'] ?? '');
$lastName = trim($_POST['last_name'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$address = trim($_POST['address'] ?? '');
$countryId = $_POST['country_id'] !== '' ? (int)$_POST['country_id'] : null;
$birthday = trim($_POST['birthday'] ?? '');
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
$birthdayValue = null;
$avatarToSave = $profileUser['avatar'];
if ($birthday !== '') {
$dateObj = DateTime::createFromFormat('Y-m-d', $birthday);
if (!$dateObj || $dateObj->format('Y-m-d') !== $birthday) {
$errorMessage = 'La data di nascita non è valida.';
} else {
$birthdayValue = $birthday;
}
}
if (!$errorMessage && $email === '') {
$errorMessage = 'Lemail è obbligatoria.';
}
if (!$errorMessage && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errorMessage = 'Lemail inserita non è valida.';
}
// Check unique email.
if (!$errorMessage) {
$stmtCheckEmail = $pdo->prepare("
SELECT id
FROM auth_users
WHERE email = ? AND id <> ?
LIMIT 1
");
$stmtCheckEmail->execute([$email, $userId]);
if ($stmtCheckEmail->fetchColumn()) {
$errorMessage = 'Questa email è già utilizzata da un altro utente.';
}
}
// Avatar upload.
if (!$errorMessage && isset($_FILES['avatar']) && $_FILES['avatar']['error'] !== UPLOAD_ERR_NO_FILE) {
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
$errorMessage = 'Errore durante il caricamento dellavatar.';
} else {
$maxFileSize = 2 * 1024 * 1024; // 2 MB
if ($_FILES['avatar']['size'] > $maxFileSize) {
$errorMessage = 'Lavatar non può superare 2 MB.';
} else {
$tmpFile = $_FILES['avatar']['tmp_name'];
$originalName = $_FILES['avatar']['name'];
$allowedMimeTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'image/gif' => 'gif',
];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($tmpFile);
if (!array_key_exists($mimeType, $allowedMimeTypes)) {
$errorMessage = 'Formato avatar non valido. Sono consentiti JPG, PNG, WEBP o GIF.';
} else {
$uploadDir = __DIR__ . '/../upload/users/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$extension = $allowedMimeTypes[$mimeType];
$safeOriginalName = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
$fileName = time() . '_' . $userId . '_' . $safeOriginalName . '.' . $extension;
$destination = $uploadDir . $fileName;
if (!move_uploaded_file($tmpFile, $destination)) {
$errorMessage = 'Impossibile salvare il file avatar.';
} else {
// Path used by pages inside userarea, for example:
// <img src="../upload/users/file.jpg">
$avatarToSave = $fileName;
}
}
}
}
}
$passwordToSave = null;
$wantsPasswordChange = ($currentPassword !== '' || $newPassword !== '' || $confirmPassword !== '');
if (!$errorMessage && $wantsPasswordChange) {
if ($currentPassword === '') {
$errorMessage = 'Inserisci la password attuale.';
} elseif ($newPassword === '') {
$errorMessage = 'Inserisci la nuova password.';
} elseif (strlen($newPassword) < 8) {
$errorMessage = 'La nuova password deve contenere almeno 8 caratteri.';
} elseif ($newPassword !== $confirmPassword) {
$errorMessage = 'La conferma password non corrisponde.';
} elseif (!password_verify($currentPassword, $profileUser['password'])) {
$errorMessage = 'La password attuale non è corretta.';
} else {
// Password is encrypted before saving.
$passwordToSave = password_hash($newPassword, PASSWORD_DEFAULT);
}
}
if (!$errorMessage) {
try {
$pdo->beginTransaction();
$stmtUpdate = $pdo->prepare("
UPDATE auth_users
SET
email = :email,
first_name = :first_name,
last_name = :last_name,
phone = :phone,
avatar = :avatar,
address = :address,
country_id = :country_id,
birthday = :birthday,
updated_at = NOW()
WHERE id = :id
LIMIT 1
");
$stmtUpdate->execute([
':email' => $email,
':first_name' => $firstName !== '' ? $firstName : null,
':last_name' => $lastName !== '' ? $lastName : null,
':phone' => $phone !== '' ? $phone : null,
':avatar' => $avatarToSave !== '' ? $avatarToSave : null,
':address' => $address !== '' ? $address : null,
':country_id' => $countryId,
':birthday' => $birthdayValue,
':id' => $userId,
]);
if ($passwordToSave !== null) {
$stmtPassword = $pdo->prepare("
UPDATE auth_users
SET password = ?, updated_at = NOW()
WHERE id = ?
LIMIT 1
");
$stmtPassword->execute([$passwordToSave, $userId]);
}
$pdo->commit();
$successMessage = $passwordToSave !== null
? 'Profilo, avatar e password aggiornati correttamente.'
: 'Profilo aggiornato correttamente.';
// Reload updated user.
$stmtProfileUser->execute([$userId]);
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
$csrfToken = $_SESSION['user_settings_csrf'];
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errorMessage = 'Errore durante il salvataggio delle impostazioni.';
}
}
}
}
$avatarPath = normalizeAvatarPath($profileUser['avatar'] ?? '');
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Impostazioni Utente <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style>
body {
background: linear-gradient(135deg, #f3f6f8, #e8eef3);
font-family: 'Segoe UI', sans-serif;
color: #2b3e50;
}
.page-content {
padding: 1.4rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.settings-wrap {
width: 100%;
max-width: 1280px;
}
h3.settings-title {
text-align: center;
font-weight: 800;
color: #2b3e50;
margin-bottom: 1.2rem;
letter-spacing: 0.3px;
}
.settings-card {
background: #fff;
border-radius: 16px;
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 16px;
overflow: hidden;
}
.settings-header {
padding: 16px 18px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
gap: 12px;
}
.settings-icon {
font-size: 1.8rem;
line-height: 1;
}
.settings-heading {
margin: 0;
font-weight: 800;
font-size: 1.1rem;
}
.settings-subtitle {
margin: 0;
color: #6b7a89;
font-size: 0.92rem;
}
.settings-body {
padding: 20px;
}
.profile-layout {
display: grid;
grid-template-columns: 280px 1fr;
gap: 24px;
align-items: flex-start;
}
.avatar-panel {
background: linear-gradient(135deg, #f7fbff, #edf5ff);
border: 1px solid #dbeafe;
border-radius: 18px;
padding: 20px;
text-align: center;
}
.avatar-box {
width: 132px;
height: 132px;
border-radius: 34px;
background: linear-gradient(135deg, #cde5ff, #dff0ff);
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-weight: 800;
color: #2b3e50;
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
margin: 0 auto 14px auto;
}
.avatar-box img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-name {
font-size: 1.1rem;
font-weight: 800;
margin: 0;
color: #2b3e50;
}
.avatar-email {
color: #6b7a89;
margin: 4px 0 14px 0;
font-size: 0.92rem;
word-break: break-word;
}
.avatar-upload-label {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
border-radius: 14px;
padding: 11px 14px;
cursor: pointer;
font-weight: 800;
color: #2b3e50;
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
transition: all 0.2s ease;
margin-top: 8px;
}
.avatar-upload-label:hover {
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
}
.avatar-upload-input {
display: none;
}
.avatar-help {
font-size: 0.82rem;
color: #6b7a89;
margin-top: 10px;
line-height: 1.35;
}
.profile-meta {
color: #6b7a89;
font-size: 0.88rem;
margin-top: 14px;
}
.form-label {
font-weight: 700;
color: #2b3e50;
margin-bottom: 6px;
}
.form-control,
.form-select {
border-radius: 12px;
border: 1px solid #d8e0e7;
padding: 10px 12px;
font-size: 0.95rem;
}
.form-control:focus,
.form-select:focus {
border-color: #8bbcf7;
box-shadow: 0 0 0 0.18rem rgba(139, 188, 247, 0.25);
}
.help-text {
font-size: 0.86rem;
color: #6b7a89;
margin-top: 6px;
}
.password-box {
background: linear-gradient(135deg, #fff7e6, #fffaf0);
border-radius: 16px;
padding: 16px;
border: 1px solid rgba(255, 184, 107, 0.45);
}
.btn-save-settings {
border: 0;
border-radius: 16px;
padding: 13px 24px;
font-size: 1rem;
font-weight: 800;
color: #fff;
background: linear-gradient(135deg, #61ce5dff, #61ce5dff);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
transition: all 0.2s ease;
}
.btn-save-settings:hover {
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
color: #fff;
}
.btn-back {
border: 0;
border-radius: 16px;
padding: 13px 20px;
font-size: 1rem;
font-weight: 800;
color: #2b3e50;
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
}
.btn-back:hover {
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
color: #2b3e50;
}
.alert {
border-radius: 16px;
border: 0;
font-weight: 600;
}
.readonly-note {
background: linear-gradient(135deg, #cde5ff, #dff0ff);
border-radius: 16px;
padding: 14px 16px;
color: #2b3e50;
font-weight: 600;
margin-bottom: 18px;
}
.actions-row {
display: flex;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-top: 18px;
}
.selected-file-name {
font-size: 0.84rem;
color: #2b3e50;
margin-top: 8px;
font-weight: 600;
word-break: break-word;
}
@media (max-width: 992px) {
.profile-layout {
grid-template-columns: 1fr;
}
.avatar-panel {
max-width: 420px;
margin: 0 auto;
width: 100%;
}
}
@media (max-width: 768px) {
.settings-body {
padding: 16px;
}
.actions-row {
flex-direction: column;
}
.btn-save-settings,
.btn-back {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="wrapper toggled">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="settings-wrap">
<h3 class="settings-title">Impostazioni Utente</h3>
<?php if ($successMessage): ?>
<div class="alert alert-success">
<?= e($successMessage); ?>
</div>
<?php endif; ?>
<?php if ($errorMessage): ?>
<div class="alert alert-danger">
<?= e($errorMessage); ?>
</div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= e($csrfToken); ?>">
<div class="settings-card">
<div class="settings-header">
<div class="settings-icon">👤</div>
<div>
<p class="settings-heading">Profilo personale</p>
<p class="settings-subtitle">Dati anagrafici, contatti e avatar utente</p>
</div>
</div>
<div class="settings-body">
<div class="readonly-note">
Ruolo, stato account e impostazioni di sicurezza avanzate non sono modificabili da questa pagina.
</div>
<div class="profile-layout">
<div class="avatar-panel">
<div class="avatar-box" id="avatarPreviewBox">
<?php if (!empty($avatarPath)): ?>
<img src="<?= e($avatarPath); ?>" class="user-img" alt="user avatar" id="avatarPreviewImage">
<?php else: ?>
<span id="avatarInitials"><?= e(getAvatarInitials($profileUser)); ?></span>
<?php endif; ?>
</div>
<p class="avatar-name">
<?= e(trim(($profileUser['first_name'] ?? '') . ' ' . ($profileUser['last_name'] ?? '')) ?: 'Utente'); ?>
</p>
<p class="avatar-email">
<?= e($profileUser['email']); ?>
</p>
<label for="avatar" class="avatar-upload-label">
Carica avatar
</label>
<input type="file"
class="avatar-upload-input"
id="avatar"
name="avatar"
accept="image/jpeg,image/png,image/webp,image/gif">
<div id="selectedFileName" class="selected-file-name"></div>
<div class="avatar-help">
Formati consentiti: JPG, PNG, WEBP, GIF.<br>
Dimensione massima: 2 MB.
</div>
<div class="profile-meta">
Stato account: <?= e($profileUser['status']); ?>
<?php if (!empty($profileUser['last_login'])): ?>
<br>Ultimo accesso: <?= e(date('d/m/Y H:i', strtotime($profileUser['last_login']))); ?>
<?php endif; ?>
</div>
</div>
<div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="first_name">Nome</label>
<input type="text"
class="form-control"
id="first_name"
name="first_name"
value="<?= e($profileUser['first_name']); ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="last_name">Cognome</label>
<input type="text"
class="form-control"
id="last_name"
name="last_name"
value="<?= e($profileUser['last_name']); ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input type="email"
class="form-control"
id="email"
name="email"
value="<?= e($profileUser['email']); ?>"
required>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">Telefono</label>
<input type="text"
class="form-control"
id="phone"
name="phone"
value="<?= e($profileUser['phone']); ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="birthday">Data di nascita</label>
<input type="date"
class="form-control"
id="birthday"
name="birthday"
value="<?= e($profileUser['birthday']); ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="country_id">Paese</label>
<select class="form-select" id="country_id" name="country_id">
<option value="">Seleziona...</option>
<?php foreach ($countries as $country): ?>
<option value="<?= (int)$country['id']; ?>"
<?= ((int)$profileUser['country_id'] === (int)$country['id']) ? 'selected' : ''; ?>>
<?= e($country['name'] . (!empty($country['iso_3166_2']) ? ' (' . $country['iso_3166_2'] . ')' : '')); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-12">
<label class="form-label" for="address">Indirizzo</label>
<input type="text"
class="form-control"
id="address"
name="address"
value="<?= e($profileUser['address']); ?>">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="settings-card">
<div class="settings-header">
<div class="settings-icon">🔐</div>
<div>
<p class="settings-heading">Cambio password</p>
<p class="settings-subtitle">Compila questa sezione solo se vuoi modificare la password</p>
</div>
</div>
<div class="settings-body">
<div class="password-box">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="current_password">Password attuale</label>
<input type="password"
class="form-control"
id="current_password"
name="current_password"
autocomplete="current-password">
</div>
<div class="col-md-4">
<label class="form-label" for="new_password">Nuova password</label>
<input type="password"
class="form-control"
id="new_password"
name="new_password"
autocomplete="new-password">
</div>
<div class="col-md-4">
<label class="form-label" for="confirm_password">Conferma nuova password</label>
<input type="password"
class="form-control"
id="confirm_password"
name="confirm_password"
autocomplete="new-password">
</div>
</div>
<div class="help-text">
Se lasci questi campi vuoti, la password attuale rimane invariata.
La nuova password deve avere almeno 8 caratteri.
</div>
</div>
<div class="actions-row">
<a href="production_dashboard.php" class="btn-back">← Torna alla dashboard</a>
<button type="submit" class="btn-save-settings">Salva impostazioni</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<?php include('jsinclude.php'); ?>
<?php include('include/footer.php'); ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const avatarInput = document.getElementById('avatar');
const previewBox = document.getElementById('avatarPreviewBox');
const selectedFileName = document.getElementById('selectedFileName');
if (!avatarInput || !previewBox) {
return;
}
avatarInput.addEventListener('change', function() {
const file = this.files && this.files[0] ? this.files[0] : null;
if (!file) {
selectedFileName.textContent = '';
return;
}
selectedFileName.textContent = file.name;
if (!file.type.startsWith('image/')) {
return;
}
const reader = new FileReader();
reader.onload = function(event) {
previewBox.innerHTML = '';
const img = document.createElement('img');
img.src = event.target.result;
img.className = 'user-img';
img.alt = 'user avatar';
previewBox.appendChild(img);
};
reader.readAsDataURL(file);
});
});
</script>
</body>
</html>