username = $username; $this->password = $password; } public function getUsername(): string { return $this->username; } public function getPassword(): string { return $this->password; } } class User { private int $id; private ?array $spaces; private string $username; private bool $isActive; private ?string $profilePicture; private ?string $bio; private ?string $website; private int $role; private ?string $passwordHash; public function __construct(int $id, ?array $spaces, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, int $role = 3, ?string $passwordHash) { error_log('Constructing a User with id: ' . $id); // 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->username = $username; $this->profilePicture = $profilePicture; $this->bio = $bio; $this->website = $website; $this->isActive = false; $this->role = $role; $this->spaces = $spaces; $this->passwordHash = $passwordHash; if ($id < 0) { $this->id = $id * -1; $stmt = 'INSERT INTO user (id, username, passwordHash, profilePicture, bio, website) VALUES (?, ?, ?, ?, ?, ?)'; exec_stmt($stmt, 'isssss', $this->id, $this->username, $passwordHash, $this->profilePicture, $this->bio, $this->website); Space::addUserToSpace($this->id, 2025); $space = new Space($id, $this->username, $this->username . "'s private Space.", 5, [], null, true); $this->spaces[] = new SpaceMember(randomId(4), $this, $space, 1, new Datetime('now'), true); } else $this->id = $id; } public static function exists($identifier): bool { if (is_int($identifier)) { $stmt = 'SELECT count(id) FROM user WHERE id = ?'; $result = exec_stmt($stmt, 'i', $identifier)->fetch_assoc(); if ($result['count(id)'] == 1) return true; else if ($result['count(id)'] > 1) return true; else return false; } else if (is_string($identifier)) { $stmt = 'SELECT count(id) FROM user WHERE username = ?'; $result = exec_stmt($stmt, 's', $identifier)->fetch_assoc(); if ($result['count(id)'] == 1) return true; else if ($result['count(id)'] > 1) return true; else return false; } return true; } public function add(SpaceMember $sm) { $this->spaces[] = $sm; } public static function retrieveFromDB($identifier): ?User { error_log('Instantiating a user'); if (is_int($identifier)) { $stmt = 'SELECT id, username, profilePicture, bio, website, role, passwordHash FROM user WHERE id = ?'; $rawUser = exec_stmt($stmt, 'i', $identifier)->fetch_assoc(); if (!$rawUser) return null; } else if (is_string($identifier)) { $stmt = 'SELECT id, username, profilePicture, bio, website, role, passwordHash FROM user WHERE username = ?'; $rawUser = exec_stmt($stmt, 'i', $identifier)->fetch_assoc(); if (!$rawUser) return null; } else { return null; } $user = new User($rawUser['id'], null, $rawUser['username'], $rawUser['profilePicture'], $rawUser['bio'], $rawUser['website'], $rawUser['role'], $rawUser['passwordHash']); $stmt = 'SELECT id, space, role, addedAt, favorited from spaceMembers WHERE user = ?'; $rels = exec_stmt($stmt, 'i', $user->getId()); if (!$rels) return null; while ($r = $rels->fetch_assoc()) $user->add(new SpaceMember($r['id'], $user, Space::retrieveFromDB($r['space'], $user->getId()), $r['role'], new DateTime($r['addedAt']), $r['favorited'])); return $user; } public static function login(LoginCredentials $credentials): ?User { $user = User::retrieveFromDB($credentials->getUsername()); if (!password_verify($credentials->getPassword(), $user->getPasswordHash())) return null; else return $user; } 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 getPasswordHash() { return $this->passwordHash; } public function getHomeSpace() { // TODO: Need to implement. Requires fleshing out additional space types. return Space::retrieveFromDB(1, 1); } public function getSpaces() { $baseSpaces = []; foreach ($this->spaces as $s) if (!$s->getSpace()->hasParent()) $baseSpaces[] = $s; return $baseSpaces; } public function getAllSpaces() { $stmt = 'SELECT space FROM spaceMembers WHERE user = ?'; $rawSpaces = exec_stmt($stmt, 'i', $this->id); $spaces = []; while ($r = $rawSpaces->fetch_assoc()) $spaces[] = Space::retrieveFromDB($r['space']); return $spaces; } public function getRole(): int { return $this->role; } public function getId(): int { return $this->id; } public function getUsername(): string { return $this->username; } 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 setIsActive(bool $isActive): void { $this->isActive = $isActive; } public static function getSignupHTML() { $experimentalHTML = '
No file selected. '; $html = '

Sign Up

'; return $html; } public static function getLoginHTML() { $stayCheckedIn = ' '; $html = '

Log In

'; return $html; } }