Current directory: /home/klas4s23/domains/585455.klas4s23.mid-ica.nl/public_html/Gastenboek/uploads
<?php
session_start();
include "db.php";
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
$user_id = $_SESSION['user_id'];
$error_message = "";
$success_message = "";
// Get current user data
try {
$stmt = $conn->prepare("SELECT username, profile_photo, bio FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
} catch (PDOException $e) {
$error_message = "Error fetching user data: " . $e->getMessage();
}
// Handle username update
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['update_username'])) {
$new_username = trim($_POST['new_username']);
if (empty($new_username)) {
$error_message = "Username cannot be empty.";
} else {
// Check if username already exists
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
$stmt->execute([$new_username, $user_id]);
if ($stmt->rowCount() > 0) {
$error_message = "This username is already taken.";
} else {
try {
$stmt = $conn->prepare("UPDATE users SET username = ? WHERE id = ?");
$stmt->execute([$new_username, $user_id]);
$success_message = "Username updated successfully!";
$user['username'] = $new_username; // Update local data
} catch (PDOException $e) {
$error_message = "Error updating username: " . $e->getMessage();
}
}
}
}
// Handle bio update
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['update_bio'])) {
$new_bio = trim($_POST['bio']);
try {
$stmt = $conn->prepare("UPDATE users SET bio = ? WHERE id = ?");
$stmt->execute([$new_bio, $user_id]);
$success_message = "Bio updated successfully!";
$user['bio'] = $new_bio; // Update local data
} catch (PDOException $e) {
$error_message = "Error updating bio: " . $e->getMessage();
}
}
// Handle password change
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['change_password'])) {
$current_password = $_POST['current_password'];
$new_password = $_POST['new_password'];
$confirm_password = $_POST['confirm_password'];
if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
$error_message = "All password fields are required.";
} elseif ($new_password !== $confirm_password) {
$error_message = "New passwords do not match.";
} else {
// Verify current password
$stmt = $conn->prepare("SELECT password FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user_data = $stmt->fetch();
if (password_verify($current_password, $user_data['password'])) {
// Update password
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
try {
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashed_password, $user_id]);
$success_message = "Password changed successfully!";
} catch (PDOException $e) {
$error_message = "Error changing password: " . $e->getMessage();
}
} else {
$error_message = "Current password is incorrect.";
}
}
}
// Handle profile photo upload
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['upload_photo'])) {
// Check for compressed image data first (client-side compression)
if (!empty($_POST['compressed_image_data'])) {
$imageData = $_POST['compressed_image_data'];
// Extract the base64 image data
if (preg_match('/^data:image\/(\w+);base64,/', $imageData, $type)) {
$imageData = substr($imageData, strpos($imageData, ',') + 1);
$imageType = strtolower($type[1]); // jpeg, png, gif
// Check if it's a valid image type
if (!in_array($imageType, ['jpeg', 'jpg', 'png', 'gif'])) {
$error_message = "Invalid image type. Only JPG, PNG and GIF images are allowed.";
} else {
// Decode base64 data
$imageData = base64_decode($imageData);
if ($imageData === false) {
$error_message = "Failed to decode image data.";
} else {
// Create uploads directory if it doesn't exist
$upload_dir = 'uploads/profile_photos/';
if (!file_exists($upload_dir)) {
if (!mkdir($upload_dir, 0755, true)) {
$error_message = "Failed to create upload directory.";
}
}
// Generate unique filename
$filename = $user_id . '_' . time() . '.' . $imageType;
$target_path = $upload_dir . $filename;
// Save the image file
if (file_put_contents($target_path, $imageData)) {
// Delete old photo if exists
if (!empty($user['profile_photo']) && file_exists($user['profile_photo'])) {
unlink($user['profile_photo']);
}
// Update database
try {
$stmt = $conn->prepare("UPDATE users SET profile_photo = ? WHERE id = ?");
$stmt->execute([$target_path, $user_id]);
$success_message = "Profile photo updated successfully!";
$user['profile_photo'] = $target_path;
} catch (PDOException $e) {
$error_message = "Database error: " . $e->getMessage();
}
} else {
$error_message = "Failed to save image.";
}
}
}
} else {
$error_message = "Invalid image data format.";
}
}
// Fall back to regular file upload if no compressed data
else if (isset($_FILES['profile_photo']) && $_FILES['profile_photo']['error'] == 0) {
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
$max_size = 5 * 1024 * 1024; // 5MB
$file = $_FILES['profile_photo'];
if (!in_array($file['type'], $allowed_types)) {
$error_message = "Invalid file type. Only JPG, PNG and GIF images are allowed.";
} elseif ($file['size'] > $max_size) {
$error_message = "File is too large. Maximum size is 5MB.";
} else {
// Create uploads directory if it doesn't exist
$upload_dir = 'uploads/profile_photos/';
if (!file_exists($upload_dir)) {
if (!mkdir($upload_dir, 0755, true)) {
$error_message = "Failed to create upload directory.";
}
}
// Make sure the directory is writable
if (!is_writable($upload_dir)) {
$error_message = "Upload directory is not writable. Please check permissions.";
} else {
// Generate unique filename
$filename = $user_id . '_' . time() . '_' . basename($file['name']);
$target_path = $upload_dir . $filename;
if (move_uploaded_file($file['tmp_name'], $target_path)) {
// Delete old photo if exists
if (!empty($user['profile_photo']) && file_exists($user['profile_photo'])) {
unlink($user['profile_photo']);
}
// Update database with new photo path
try {
$stmt = $conn->prepare("UPDATE users SET profile_photo = ? WHERE id = ?");
$stmt->execute([$target_path, $user_id]);
$success_message = "Profile photo updated successfully!";
$user['profile_photo'] = $target_path; // Update local data
} catch (PDOException $e) {
$error_message = "Error updating profile photo in database: " . $e->getMessage();
}
} else {
$error_message = "Failed to move uploaded file to the target location.";
}
}
}
} else {
if (!isset($_FILES['profile_photo'])) {
$error_message = "Error: No file was uploaded. The profile_photo field is missing.";
} elseif ($_FILES['profile_photo']['error'] != 0) {
// Show detailed error based on the error code
switch($_FILES['profile_photo']['error']) {
case UPLOAD_ERR_INI_SIZE:
$error_message = "The uploaded file exceeds the upload_max_filesize directive in php.ini.";
break;
case UPLOAD_ERR_FORM_SIZE:
$error_message = "The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.";
break;
case UPLOAD_ERR_PARTIAL:
$error_message = "The uploaded file was only partially uploaded.";
break;
case UPLOAD_ERR_NO_FILE:
$error_message = "No file was uploaded. Please select an image.";
break;
case UPLOAD_ERR_NO_TMP_DIR:
$error_message = "Missing a temporary folder.";
break;
case UPLOAD_ERR_CANT_WRITE:
$error_message = "Failed to write file to disk.";
break;
case UPLOAD_ERR_EXTENSION:
$error_message = "A PHP extension stopped the file upload.";
break;
default:
$error_message = "Unknown upload error: " . $_FILES['profile_photo']['error'];
}
}
}
}
// Handle profile photo removal
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['remove_photo'])) {
if (!empty($user['profile_photo'])) {
// Delete the file if it exists
if (file_exists($user['profile_photo'])) {
unlink($user['profile_photo']);
}
// Update database to remove photo path
try {
$stmt = $conn->prepare("UPDATE users SET profile_photo = NULL WHERE id = ?");
$stmt->execute([$user_id]);
$success_message = "Profile photo removed.";
$user['profile_photo'] = null; // Update local data
} catch (PDOException $e) {
$error_message = "Error removing profile photo: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Profile - ELearner</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="font-styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<style>
.profile-photo {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 50%;
}
.profile-photo-container {
position: relative;
width: 150px;
margin: 0 auto;
}
.photo-overlay {
position: absolute;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.preview-container {
width: 150px;
height: 150px;
margin: 10px auto;
overflow: hidden;
border-radius: 50%;
border: 2px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: cover;
width: 100%;
height: 100%;
}
.file-info {
font-size: 0.8rem;
color: #666;
margin-top: 0.5rem;
}
.progress-bar {
height: 5px;
background-color: #e2e8f0;
border-radius: 2px;
overflow: hidden;
margin-top: 5px;
}
.progress-bar-inner {
height: 100%;
background-color: #3b82f6;
width: 0%;
transition: width 0.3s;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col">
<div class="main-content flex-1">
<div class="container mx-auto px-4 py-4 sm:py-8">
<header class="bg-white rounded-lg shadow-md p-4 sm:p-6 mb-6 sm:mb-8">
<div class="flex flex-wrap items-center justify-between">
<div class="flex items-center">
<a href="homepage.php">
<img src="images/icon-e-learner-blue.png" alt="ELearner Logo" class="h-10 sm:h-12 mr-3 sm:mr-4">
</a>
<div>
<h1 class="text-2xl sm:text-3xl font-bold text-blue-600">ELearner</h1>
<p class="text-sm sm:text-base text-gray-600">My Profile</p>
</div>
</div>
<nav id="desktopMenu" class="hidden md:block">
<ul class="flex space-x-4 sm:space-x-6">
<li><a href="homepage.php" class="text-blue-500 font-medium hover:underline">Home</a></li>
<li><a href="index.php" class="text-blue-500 font-medium hover:underline">Vocabulary Tool</a></li>
<li><a href="my_lists.php" class="text-blue-500 font-medium hover:underline">My Lists</a></li>
<li><a href="list_hub.php" class="text-blue-500 font-medium hover:underline">List Hub</a></li>
<li><a href="profile.php" class="text-blue-500 font-medium hover:underline">My Profile</a></li>
<?php if(isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1): ?>
<li><a href="admin.php" class="text-purple-500 font-medium hover:underline">Admin</a></li>
<?php endif; ?>
<li><a href="logout.php" class="text-red-500 font-medium hover:underline">Logout</a></li>
<li>
<button id="fontToggleBtn" class="bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600">
Toggle Font
</button>
</li>
</ul>
</nav>
<!-- Mobile menu button -->
<button id="mobileMenuBtn" class="md:hidden p-2 rounded text-blue-600 hover:bg-blue-100">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
<!-- Mobile menu, hidden by default -->
<nav id="mobileMenu" class="md:hidden hidden mt-4 pb-2">
<ul class="flex flex-col space-y-3">
<li><a href="homepage.php" class="block text-blue-500 font-medium hover:underline">Home</a></li>
<li><a href="index.php" class="block text-blue-500 font-medium hover:underline">Vocabulary Tool</a></li>
<li><a href="my_lists.php" class="block text-blue-500 font-medium hover:underline">My Lists</a></li>
<li><a href="list_hub.php" class="block text-blue-500 font-medium hover:underline">List Hub</a></li>
<li><a href="profile.php" class="block text-blue-500 font-medium hover:underline">My Profile</a></li>
<?php if(isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1): ?>
<li><a href="admin.php" class="block text-purple-500 font-medium hover:underline">Admin</a></li>
<?php endif; ?>
<li><a href="logout.php" class="block text-red-500 font-medium hover:underline">Logout</a></li>
<li>
<button id="mobileFontToggleBtn" class="bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600">
Toggle Font
</button>
</li>
</ul>
</nav>
</header>
<div class="flex flex-col md:flex-row gap-6">
<!-- Profile Summary Card -->
<div class="w-full md:w-1/3">
<div class="bg-white rounded-lg shadow-md p-6 text-center">
<h2 class="text-xl font-bold mb-4">My Profile</h2>
<div class="profile-photo-container mb-4">
<?php if (!empty($user['profile_photo']) && file_exists($user['profile_photo'])): ?>
<img src="<?= htmlspecialchars($user['profile_photo']) ?>" alt="Profile Photo" class="profile-photo">
<?php else: ?>
<div class="profile-photo bg-gray-200 flex items-center justify-center">
<i class="fas fa-user text-gray-400 text-5xl"></i>
</div>
<?php endif; ?>
<label for="photoUploadInput" class="photo-overlay" title="Change photo">
<i class="fas fa-camera"></i>
</label>
</div>
<h3 class="text-lg font-medium"><?= htmlspecialchars($user['username']) ?></h3>
<div class="mt-4">
<form action="profile.php" method="post" enctype="multipart/form-data" class="border-t pt-4 mt-2">
<h4 class="font-medium text-gray-700 mb-2">Change Profile Picture</h4>
<div class="flex flex-col items-center">
<div class="relative w-full max-w-sm mb-3">
<input type="file" id="photoUploadInput" name="profile_photo"
accept="image/jpeg, image/png, image/gif" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10">
<div class="flex items-center">
<label for="photoUploadInput" class="flex-grow px-4 py-2 bg-gray-200 text-gray-700 rounded-l border border-gray-300 hover:bg-gray-300 transition duration-200 text-center overflow-hidden whitespace-nowrap overflow-ellipsis">
<span id="fileNameDisplay">Choose a file...</span>
</label>
<span class="bg-blue-500 text-white px-4 py-2 rounded-r flex items-center">
<i class="fas fa-upload mr-2"></i> Browse
</span>
</div>
<p class="text-xs text-gray-500 mt-1">Supported formats: JPEG, PNG, GIF (max 5MB)</p>
</div>
<input type="hidden" name="upload_photo" value="1">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors duration-200 flex items-center">
<i class="fas fa-cloud-upload-alt mr-2"></i> Upload Photo
</button>
</div>
</form>
</div>
<?php if (!empty($user['profile_photo'])): ?>
<form method="post" class="mt-2">
<button type="submit" name="remove_photo" class="text-red-500 text-sm hover:underline">
Remove Photo
</button>
</form>
<?php endif; ?>
</div>
</div>
<!-- Profile Edit Forms -->
<div class="w-full md:w-2/3">
<div class="bg-white rounded-lg shadow-md p-6">
<?php if ($error_message): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4" role="alert">
<p><?php echo $error_message; ?></p>
</div>
<?php endif; ?>
<?php if ($success_message): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4" role="alert">
<p><?php echo $success_message; ?></p>
</div>
<?php endif; ?>
<h2 class="text-xl font-bold mb-6">Edit Profile</h2>
<!-- Update Username Form -->
<div class="mb-8 pb-6 border-b border-gray-200">
<h3 class="text-lg font-semibold mb-4">Update Username</h3>
<form method="post" class="space-y-4">
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-grow">
<input type="text" name="new_username" value="<?= htmlspecialchars($user['username']) ?>"
class="w-full p-2 border rounded" required>
</div>
<div>
<button type="submit" name="update_username" class="w-full sm:w-auto bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
Update Username
</button>
</div>
</div>
</form>
</div>
<!-- Update Bio Form -->
<div class="mb-8 pb-6 border-b border-gray-200">
<h3 class="text-lg font-semibold mb-4">Update Bio</h3>
<form method="post" class="space-y-4">
<div>
<label for="bio" class="block text-gray-700 mb-1">Bio (optional)</label>
<textarea id="bio" name="bio" rows="4" class="w-full p-2 border rounded"
placeholder="Tell others about yourself..."><?= htmlspecialchars($user['bio'] ?? '') ?></textarea>
<p class="text-xs text-gray-500 mt-1">Your bio will be visible on your public profile.</p>
</div>
<div>
<button type="submit" name="update_bio" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
Update Bio
</button>
</div>
</form>
</div>
<!-- Change Password Form -->
<div>
<h3 class="text-lg font-semibold mb-4">Change Password</h3>
<form method="post" class="space-y-4">
<div>
<label for="current_password" class="block text-gray-700 mb-1">Current Password</label>
<input type="password" id="current_password" name="current_password"
class="w-full p-2 border rounded" required>
</div>
<div>
<label for="new_password" class="block text-gray-700 mb-1">New Password</label>
<input type="password" id="new_password" name="new_password"
class="w-full p-2 border rounded" required>
</div>
<div>
<label for="confirm_password" class="block text-gray-700 mb-1">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password"
class="w-full p-2 border rounded" required>
</div>
<div>
<button type="submit" name="change_password" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
Change Password
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include "footer.php"; ?>
<script>
// Mobile menu toggle
document.getElementById("mobileMenuBtn").addEventListener("click", function() {
const mobileMenu = document.getElementById("mobileMenu");
mobileMenu.classList.toggle("hidden");
});
// Copy font toggle functionality to mobile button
if (document.getElementById("mobileFontToggleBtn")) {
document.getElementById("mobileFontToggleBtn").addEventListener("click", function() {
document.getElementById("fontToggleBtn").click();
});
}
// Image compression and preview code
document.addEventListener('DOMContentLoaded', function() {
const photoInput = document.getElementById('photoUploadInput');
const previewContainer = document.getElementById('previewContainer');
const previewImage = document.getElementById('previewImage');
const fileInfo = document.getElementById('fileInfo');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const uploadButton = document.getElementById('uploadButton');
const compressedImageInput = document.getElementById('compressedImageData');
const photoForm = document.getElementById('photoUploadForm');
// Maximum file size (in bytes) - 2MB
const MAX_FILE_SIZE = 2 * 1024 * 1024;
// Handle file selection
photoInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) {
return;
}
// Show preview container and file info
previewContainer.classList.remove('hidden');
fileInfo.classList.remove('hidden');
const reader = new FileReader();
// Create file reader to display preview
reader.onload = function(e) {
previewImage.src = e.target.result;
// Show the original image preview
fileInfo.textContent = `Original: ${formatFileSize(file.size)}`;
if (file.size > MAX_FILE_SIZE) {
compressImage(e.target.result);
} else {
compressedImageInput.value = e.target.result;
// If file is small enough, just use it directly
}
};
reader.readAsDataURL(file);
});
// Handle form submission
photoForm.addEventListener('submit', function(e) {
if (compressedImageInput.value === '') {
const file = photoInput.files[0];
if (file && file.size > MAX_FILE_SIZE) {
alert("Please wait for image compression to complete.");
e.preventDefault();
}
}
});
// Image compression function
function compressImage(dataUrl) {
const img = new Image();
// Create an image element to load the data URL
img.onload = function() {
let width = img.width;
let height = img.height;
// Calculate new dimensions while maintaining aspect ratio
const maxDimension = 1200; // Maximum dimension for resized image
if (width > height && width > maxDimension) {
width = maxDimension;
height = (height / width) * maxDimension;
} else if (height > maxDimension) {
height = maxDimension;
width = (width / height) * maxDimension;
}
const canvas = document.createElement('canvas');
// Create canvas for resizing
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Draw image on canvas (resizing it)
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.7);
// Get compressed data URL (0.7 quality for JPEG)
previewImage.src = compressedDataUrl;
// Update preview and info
compressedImageInput.value = compressedDataUrl;
// Set the compressed image data in the form
const estimatedSize = Math.round(compressedDataUrl.length * 0.75);
// Estimate compressed file size (roughly)
fileInfo.textContent = `Original: ${formatFileSize(img.src.length)} → Compressed: ${formatFileSize(estimatedSize)}`;
progressBar.style.width = '100%';
// Hide progress after a short delay
setTimeout(() => {
progressContainer.classList.add('hidden');
progressBar.style.width = '0%';
}, 500);
};
img.src = dataUrl;
progressContainer.classList.remove('hidden');
// Show progress indicator
progressBar.style.width = '10%';
}
// Helper function to format file size
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
});
// Update file name display when a file is selected
document.getElementById('photoUploadInput').addEventListener('change', function(e) {
const fileName = e.target.files[0] ? e.target.files[0].name : 'Choose a file...';
document.getElementById('fileNameDisplay').textContent = fileName;
});
</script>
<script src="font-toggle.js"></script>
</body>
</html>