username = $username; $this->password = $password; } public function getUsername(): string { return $this->username; } public function getPassword(): string { return $this->password; } } class User { private int $userId; private string $username; private ?DateTime $createdAt; private ?DateTime $lastActive; private bool $isActive; private ?string $profilePicture; private ?string $bio; private ?string $website; public function __construct(int $userId, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, ?mysqli $db = null) { // Basic XSS prevention on construction (can be enhanced) $username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); $bio = htmlspecialchars($bio ?? '', ENT_QUOTES, 'UTF-8'); $website = htmlspecialchars($website ?? '', ENT_QUOTES, 'UTF-8'); $profilePicture = htmlspecialchars($profilePicture ?? 'default-profile.png', ENT_QUOTES, 'UTF-8'); // Basic SQL injection prevention (should primarily rely on prepared statements) if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $bio) || preg_match('/[\'";\-\_]/', $website) || preg_match('/[\'";\-\_]/', $profilePicture)) { throw new InvalidArgumentException('Invalid characters in user data.'); } $this->userId = $userId; $this->username = $username; $this->profilePicture = $profilePicture; $this->bio = $bio; $this->website = $website; $this->createdAt = new DateTime(); $this->lastActive = null; $this->isActive = false; if ($db) { $stmt = $db->prepare('INSERT INTO user (user_id, username, profile_picture, bio, website) VALUES (?, ?, ?, ?, ?)'); if ($stmt) { $stmt->bind_param('issss', $this->userId, $this->username, $this->profilePicture, $this->bio, $this->website); $stmt->execute(); $stmt->close(); } else { error_log('Error preparing statement: ' . $db->error); } } else { // Consider logging or throwing an exception if no database connection is provided error_log('Database connection not provided during User creation.'); } } public static function exists(int $id) { $stmt = 'SELECT count(username) FROM user WHERE id = ?'; echo pretty_dump(exec_stmt($stmt, 'i', $id)->fetch_assoc()); } public static function login(LoginCredentials $credentials, ?mysqli $db): ?User { if (!$db) { error_log('Database connection not provided for login.'); return null; } $username = $credentials->getUsername(); $password = $credentials->getPassword(); $stmt = $db->prepare('SELECT user_id, username, password_hash, profile_picture, bio, website FROM user WHERE username = ?'); if ($stmt) { $stmt->bind_param('s', $username); $stmt->execute(); $result = $stmt->get_result(); $user = $result->fetch_assoc(); $stmt->close(); if ($user && hash('sha256', $password) === $user['password_hash']) { return new User( (int) $user['user_id'], $user['username'], $user['profile_picture'], $user['bio'], $user['website'] ); } } else { error_log('Error preparing statement: ' . $db->error); } return null; } public static function logout(): void { foreach (array_keys($_SESSION) as $key) { unset($_SESSION[$key]); } // Consider destroying the session cookie as well: session_destroy(); header('Location: .'); // Redirect to the homepage or login page exit(); } public function getUserId(): int { return $this->userId; } public function getUsername(): string { return $this->username; } public function getCreatedAt(): ?DateTime { return $this->createdAt; } public function getLastActive(): ?DateTime { return $this->lastActive; } public function isActive(): bool { return $this->isActive; } public function getProfilePicture(): ?string { return $this->profilePicture; } public function getBio(): ?string { return $this->bio; } public function getWebsite(): ?string { return $this->website; } public function setLastActive(?DateTime $lastActive): void { $this->lastActive = $lastActive; } public function setIsActive(bool $isActive): void { $this->isActive = $isActive; } public function toggleActive(?mysqli $db): bool { if (!$db) { error_log('Database connection not provided for toggleActive.'); return false; } $this->isActive = !$this->isActive; $stmt = $db->prepare('UPDATE user SET is_active = ? WHERE user_id = ?'); if ($stmt) { $stmt->bind_param('ii', (int) $this->isActive, $this->userId); $result = $stmt->execute(); $stmt->close(); return $result; } else { error_log('Error preparing statement: ' . $db->error); return false; } } public function updateUsername(string $newUsername, ?mysqli $db): bool { $newUsername = htmlspecialchars($newUsername, ENT_QUOTES, 'UTF-8'); if (preg_match('/[\'";\-\_]/', $newUsername)) { throw new InvalidArgumentException('Invalid characters in username.'); } if (!$db) { error_log('Database connection not provided for updateUsername.'); return false; } $stmt = $db->prepare('UPDATE user SET username = ? WHERE user_id = ?'); if ($stmt) { $stmt->bind_param('si', $newUsername, $this->userId); $result = $stmt->execute(); $stmt->close(); if ($result) { $this->username = $newUsername; return true; } } else { error_log('Error preparing statement: ' . $db->error); } return false; } public function updatePassword(string $newPassword, ?mysqli $db): bool { $newPasswordHash = hash('sha256', $newPassword); if (!$db) { error_log('Database connection not provided for updatePassword.'); return false; } $stmt = $db->prepare('UPDATE user SET password_hash = ? WHERE user_id = ?'); if ($stmt) { $stmt->bind_param('si', $newPasswordHash, $this->userId); $result = $stmt->execute(); $stmt->close(); return $result; } else { error_log('Error preparing statement: ' . $db->error); } return false; } public function updateProfilePicture(?string $newProfilePicture, ?mysqli $db): bool { $newProfilePicture = htmlspecialchars($newProfilePicture ?? 'default-profile.png', ENT_QUOTES, 'UTF-8'); if (preg_match('/[\'";\-\_]/', $newProfilePicture)) { throw new InvalidArgumentException('Invalid characters in profile picture filename.'); } if (!$db) { error_log('Database connection not provided for updateProfilePicture.'); return false; } $stmt = $db->prepare('UPDATE user SET profile_picture = ? WHERE user_id = ?'); if ($stmt) { $stmt->bind_param('si', $newProfilePicture, $this->userId); $result = $stmt->execute(); $stmt->close(); if ($result) { $this->profilePicture = $newProfilePicture; return true; } } else { error_log('Error preparing statement: ' . $db->error); } return false; } public function followUser(int $followingUserId, bool $notify = false, ?mysqli $db): bool { if (!$db) { error_log('Database connection not provided for followUser.'); return false; } $stmt = $db->prepare('INSERT INTO follows (follower_user_id, following_user_id, notify_user) VALUES (?, ?, ?)'); if ($stmt) { $stmt->bind_param('iii', $this->userId, $followingUserId, (int) $notify); $result = $stmt->execute(); $stmt->close(); return $result; } else { error_log('Error preparing statement: ' . $db->error); return false; } } public function getFollowers(?mysqli $db): array { if (!$db) { error_log('Database connection not provided for getFollowers.'); return []; } $stmt = $db->prepare('SELECT u.user_id, u.username, u.profile_picture, u.bio, u.website FROM follows f JOIN user u ON f.follower_user_id = u.user_id WHERE f.following_user_id = ?'); if ($stmt) { $stmt->bind_param('i', $this->userId); $stmt->execute(); $result = $stmt->get_result(); $followers = []; while ($row = $result->fetch_assoc()) { $followers[] = new User( (int) $row['user_id'], $row['username'], $row['profile_picture'], $row['bio'], $row['website'] ); } $stmt->close(); return $followers; } else { error_log('Error preparing statement: ' . $db->error); return []; } } public function getFollowing(?mysqli $db): array { if (!$db) { error_log('Database connection not provided for getFollowing.'); return []; } $stmt = $db->prepare('SELECT u.user_id, u.username, u.profile_picture, u.bio, u.website FROM follows f JOIN user u ON f.following_user_id = u.user_id WHERE f.follower_user_id = ?'); if ($stmt) { $stmt->bind_param('i', $this->userId); $stmt->execute(); $result = $stmt->get_result(); $following = []; while ($row = $result->fetch_assoc()) { $following[] = new User( (int) $row['user_id'], $row['username'], $row['profile_picture'], $row['bio'], $row['website'] ); } $stmt->close(); return $following; } else { error_log('Error preparing statement: ' . $db->error); return []; } } // Static method to fetch a User by ID (example using mysqli) public static function findById(int $userId, ?mysqli $db): ?User { if (!$db) { error_log('Database connection not provided for findById.'); return null; } $stmt = $db->prepare('SELECT user_id, username, profile_picture, bio, website FROM user WHERE user_id = ?'); if ($stmt) { $stmt->bind_param('i', $userId); $stmt->execute(); $result = $stmt->get_result(); $user = $result->fetch_assoc(); $stmt->close(); if ($user) { return new User( (int) $user['user_id'], $user['username'], $user['profile_picture'], $user['bio'], $user['website'] ); } } else { error_log('Error preparing statement: ' . $db->error); } return null; } } class Author extends User {}