feat(avatar): implement avatar upload processing and validation, update user interface for avatar display

This commit is contained in:
2026-09-11 16:00:48 +02:00
parent e012d6d21c
commit 35ce20cd59
12 changed files with 210 additions and 57 deletions
+37 -39
View File
@@ -219,43 +219,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = "De afbeelding is te groot (maximaal 4MB toegestaan).";
$messageType = 'error';
} else {
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowedTypes)) {
$message = "Alleen JPG, PNG, WebP of GIF afbeeldingen zijn toegestaan.";
$processed = fociz_process_avatar_upload($file, $userId);
if (!$processed['success']) {
$message = $processed['error'] ?? "Fout bij verwerken van de afbeelding.";
$messageType = 'error';
} else {
$ext = match($mime) {
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'image/gif' => 'gif',
default => 'jpg'
};
$uploadDir = __DIR__ . '/assets/uploads/avatars';
if (!is_dir($uploadDir)) {
@mkdir($uploadDir, 0755, true);
}
$filename = "avatar_{$userId}_" . time() . "_" . bin2hex(random_bytes(4)) . ".{$ext}";
$targetPath = $uploadDir . '/' . $filename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$webPath = "/assets/uploads/avatars/{$filename}";
$stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?");
$stmtUp->execute([$webPath, $userId]);
$_SESSION['user_avatar_url'] = $webPath;
$user['avatar_url'] = $webPath;
$message = "Uw profielfoto is succesvol geüpload en ingesteld!";
$messageType = 'success';
} else {
$message = "Er is een fout opgetreden bij het verplaatsen van het geüploade bestand.";
$messageType = 'error';
}
$avatarUri = $processed['data_uri'];
$stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?");
$stmtUp->execute([$avatarUri, $userId]);
$_SESSION['user_avatar_url'] = $avatarUri;
$user['avatar_url'] = $avatarUri;
$message = "Uw profielfoto is succesvol geüpload en permanent bewaard!";
$messageType = 'success';
}
}
}
@@ -277,6 +252,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmtUser = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmtUser->execute([$userId]);
$userProfile = $stmtUser->fetch();
if ($userProfile) {
$userProfile['avatar_url'] = fociz_get_valid_avatar_url($userProfile['avatar_url'] ?? null, $userId);
}
// Outlook status ophalen
$stmtOutlook = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
@@ -316,7 +294,10 @@ require_once __DIR__ . '/includes/header.php';
<div class="flex items-center space-x-4 sm:space-x-5">
<div class="w-16 h-16 sm:w-20 sm:h-20 rounded-2xl bg-gradient-to-tr from-indigo-600 to-sky-500 p-0.5 shadow-sm flex-shrink-0 overflow-hidden">
<?php if (!empty($userProfile['avatar_url'])): ?>
<img src="<?= htmlspecialchars($userProfile['avatar_url']) ?>" alt="Avatar" class="w-full h-full rounded-2xl object-cover bg-white dark:bg-[#0c0f17]">
<img src="<?= htmlspecialchars($userProfile['avatar_url']) ?>" alt="Avatar" class="w-full h-full rounded-2xl object-cover bg-white dark:bg-[#0c0f17]" onerror="this.classList.add('hidden'); if(this.nextElementSibling) this.nextElementSibling.classList.remove('hidden');">
<div class="hidden w-full h-full rounded-2xl bg-white dark:bg-[#0c0f17] flex items-center justify-center font-bold text-2xl sm:text-3xl text-indigo-600 dark:text-indigo-400 uppercase">
<?= strtoupper(substr($userProfile['name'], 0, 1)) ?>
</div>
<?php else: ?>
<div class="w-full h-full rounded-2xl bg-white dark:bg-[#0c0f17] flex items-center justify-center font-bold text-2xl sm:text-3xl text-indigo-600 dark:text-indigo-400 uppercase">
<?= strtoupper(substr($userProfile['name'], 0, 1)) ?>
@@ -1071,16 +1052,17 @@ require_once __DIR__ . '/includes/header.php';
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Nieuw Wachtwoord (min. 8 tekens)
Nieuw Wachtwoord (min. 12 tekens)
</label>
<input type="password" name="new_password" required minlength="8" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<input type="password" name="new_password" required minlength="12" placeholder="Minimaal 12 tekens" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<p class="text-[10px] text-slate-400 dark:text-slate-500 mt-1">Minimaal 12 tekens (incl. hoofdletter, kleine letter, cijfer en speciaal teken).</p>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Bevestig Nieuw Wachtwoord
</label>
<input type="password" name="confirm_password" required minlength="8" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<input type="password" name="confirm_password" required minlength="12" placeholder="Herhaal nieuw wachtwoord" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div class="flex justify-end pt-2">
@@ -1151,6 +1133,22 @@ function previewUploadAvatar(input) {
nameEl.textContent = '📄 ' + file.name + ' (' + Math.round(file.size / 1024) + ' KB)';
nameEl.classList.remove('hidden');
}
// Live thumbnail weergave
const reader = new FileReader();
reader.onload = function(e) {
let prevImg = document.getElementById('uploadAvatarLivePreview');
if (!prevImg && nameEl && nameEl.parentNode) {
prevImg = document.createElement('img');
prevImg.id = 'uploadAvatarLivePreview';
prevImg.className = 'w-16 h-16 rounded-full mx-auto my-2 object-cover border-2 border-indigo-500 shadow-sm';
nameEl.parentNode.insertBefore(prevImg, nameEl);
}
if (prevImg) {
prevImg.src = e.target.result;
prevImg.classList.remove('hidden');
}
};
reader.readAsDataURL(file);
}
}