user = new User(); } public function show(): void { if (!isset($_SESSION['user_id'])) { header('Location: /login'); return; } $user = $this->user->findById($_SESSION['user_id']); $content = __DIR__ . '/../Views/profile/edit.php'; include __DIR__ . '/../Views/layouts/main.php'; } public function updateUsername(): void { if (!isset($_SESSION['user_id'])) { header('Location: /login'); return; } if (!Csrf::validate($_POST['csrf_token'] ?? '')) { Flash::set('error', 'Invalid CSRF token.'); header('Location: /profile'); return; } $username = trim($_POST['username'] ?? ''); if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) { Flash::set('error', 'Username must be 3-20 characters (letters, numbers, underscores).'); header('Location: /profile'); return; } $existing = $this->user->findByUsername($username); if ($existing && $existing['id'] !== $_SESSION['user_id']) { Flash::set('error', 'Username is already taken.'); header('Location: /profile'); return; } $this->user->updateUsername($_SESSION['user_id'], $username); // Keep the session in sync so the nav bar shows the new name $_SESSION['username'] = $username; Flash::set('success', 'Username updated.'); header('Location: /profile'); } public function updateEmail(): void { if (!isset($_SESSION['user_id'])) { header('Location: /login'); return; } if (!Csrf::validate($_POST['csrf_token'] ?? '')) { Flash::set('error', 'Invalid CSRF token.'); header('Location: /profile'); return; } $email = trim($_POST['email'] ?? ''); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { Flash::set('error', 'Invalid email address.'); header('Location: /profile'); return; } $existing = $this->user->findByEmail($email); if ($existing && $existing['id'] !== $_SESSION['user_id']) { Flash::set('error', 'Email is already registered.'); header('Location: /profile'); return; } // Changing email requires re-verification: the user must confirm // they own the new address before they can log in again $token = $this->user->updateEmail($_SESSION['user_id'], $email); Mail::sendVerification($email, $token); // Log the user out so they must re-verify session_destroy(); // Start a fresh session for the flash message session_start(); Flash::set('success', 'Email updated. Check your new email to re-verify your account.'); header('Location: /login'); } public function updatePassword(): void { if (!isset($_SESSION['user_id'])) { header('Location: /login'); return; } if (!Csrf::validate($_POST['csrf_token'] ?? '')) { Flash::set('error', 'Invalid CSRF token.'); header('Location: /profile'); return; } $currentPassword = $_POST['current_password'] ?? ''; $newPassword = $_POST['new_password'] ?? ''; $confirmPassword = $_POST['new_password_confirm'] ?? ''; $user = $this->user->findById($_SESSION['user_id']); // Verify the current password so an attacker who steals a session // can't silently change the password if (!password_verify($currentPassword, $user['password_hash'])) { Flash::set('error', 'Current password is incorrect.'); header('Location: /profile'); return; } if (\strlen($newPassword) < 8) { Flash::set('error', 'New password must be at least 8 characters.'); header('Location: /profile'); return; } if ($newPassword !== $confirmPassword) { Flash::set('error', 'New passwords do not match.'); header('Location: /profile'); return; } $this->user->updatePassword($user['id'], $newPassword); Flash::set('success', 'Password updated.'); header('Location: /profile'); } public function updateNotifications(): void { if (!isset($_SESSION['user_id'])) { header('Location: /login'); return; } if (!Csrf::validate($_POST['csrf_token'] ?? '')) { Flash::set('error', 'Invalid CSRF token.'); header('Location: /profile'); return; } // Checkbox value: present in POST when checked, absent when unchecked $notify = isset($_POST['notify_comments']); $this->user->updateNotifyComments($_SESSION['user_id'], $notify); Flash::set('success', 'Notification preferences updated.'); header('Location: /profile'); } }