461 lines
16 KiB
PHP
461 lines
16 KiB
PHP
<?php
|
|
|
|
class LoginCredentials
|
|
{
|
|
private string $username;
|
|
private string $password;
|
|
|
|
public function __construct(string $username, string $password)
|
|
{
|
|
// More intensive SQL injection checks (example - adapt as needed)
|
|
if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $password)) {
|
|
throw new InvalidArgumentException('Invalid characters in username or password.');
|
|
}
|
|
// Consider using a more robust validation library
|
|
|
|
$this->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;
|
|
|
|
public function __construct(int $id = -1, ?array $spaces, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, int $role = 3, ?string $passwordHash)
|
|
{
|
|
// 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 = null;
|
|
|
|
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);
|
|
new Space($id, $this->username, $this->username . "'s private Space.", 5, [$this], [], null, 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 static function retrieveFromDB(int $userId): ?User
|
|
{
|
|
$stmt = 'SELECT id, username, profilePicture, bio, website, role FROM user WHERE id = ?';
|
|
$user = exec_stmt($stmt, 'i', $userId)->fetch_assoc();
|
|
|
|
if ($user)
|
|
return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website'], $user['role'], null);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
public static function getSignupHTML()
|
|
{
|
|
$experimentalHTML = '
|
|
<form method="post" enctype="multipart/form-data">
|
|
|
|
<input name="required_field" id="required_field" value="">
|
|
|
|
<input type="hidden" name="crop_x" value="" id="crop_x">
|
|
<input type="hidden" name="crop_y" value="" id="crop_y">
|
|
<input type="hidden" name="crop_width" value="" id="crop_width">
|
|
|
|
<label for="email">Email Address</label>
|
|
<input id="email" name="email" required>
|
|
|
|
<label for="profile_picture_input" class="upload_button modal_button" id="profile_cropper_button">Upload a Profile Picture</label>
|
|
<input id="profile_picture_input" class="file_input" name="profile_picture" type="file" accept="image/png, image/jpeg, image/jpg">
|
|
<small class="file_input_feedback">No file selected.</small>
|
|
|
|
<label class="form_checkbox_container">Stay Logged In?
|
|
<input name="stay_logged_in" type="checkbox" value="true">
|
|
<span class="checkmark"></span>
|
|
</label>
|
|
';
|
|
|
|
$html = '
|
|
<div class="med_width std_border padding center modalContent">
|
|
<h3 class="underline">Sign Up</h3>
|
|
<div class="line"></div>
|
|
<form method="post" action="director.php">
|
|
<input type="hidden" name="formID" value="signup">
|
|
|
|
<label for="username">Username</label>
|
|
<input id="username" name="username" required autofocus>
|
|
|
|
<label for="password">Password</label>
|
|
<input id="password" name="password" type="password" required>
|
|
|
|
<label for="vPassword">Verify Password</label>
|
|
<input id="vPassword" name="vPassword" type="password" required>
|
|
|
|
<input class="button" type="submit" value="Sign Up">
|
|
</form>
|
|
|
|
<div id="signupError"></div>
|
|
</div>
|
|
';
|
|
|
|
return $html;
|
|
}
|
|
|
|
public static function getLoginHTML()
|
|
{
|
|
$stayCheckedIn = '
|
|
<label class="form_checkbox_container">Stay Logged In?
|
|
<input name="stay_logged_in" type="checkbox" value="true">
|
|
<span class="checkmark"></span>
|
|
</label>
|
|
';
|
|
|
|
$html = '
|
|
<div class="med_width std_border padding center modalContent">
|
|
<h3 class="underline">Log In</h3>
|
|
<div class="line"></div>
|
|
<form method="post" action="director.php">
|
|
<input type="hidden" name="formID" value="login">
|
|
|
|
<label for="username">Username</label>
|
|
<input id="username" name="username" spellcheck="false" required autofocus>
|
|
|
|
<label for="password">Password</label>
|
|
<input id="password" name="password" type="password" spellcheck="false" required>
|
|
|
|
<input class="button" type="submit" value="Log In">
|
|
</form>
|
|
|
|
<div id="loginError"></div>
|
|
</div>
|
|
';
|
|
|
|
return $html;
|
|
}
|
|
|
|
public static function login(LoginCredentials $credentials): ?User
|
|
{
|
|
$stmt = 'SELECT id, username, email, passwordHash, role, profilePicture, bio, website FROM user WHERE username = ?';
|
|
$user = exec_stmt($stmt, 's', $credentials->getUsername())->fetch_assoc();
|
|
|
|
if (!password_verify($credentials->getPassword(), $user['passwordHash']))
|
|
return null;
|
|
else {
|
|
$spaces = [];
|
|
$stmt = 'SELECT space FROM spaceMembers WHERE user = ?';
|
|
$rawSpaces = exec_stmt($stmt, 'i', $user['id'])->fetch_assoc();
|
|
|
|
if ($rawSpaces)
|
|
foreach ($rawSpaces as $s)
|
|
$spaces[] = serialize(Space::retrieveFromDB($s));
|
|
|
|
return new User($user['id'], $spaces, $user['username'], $user['profilePicture'], $user['bio'], $user['website'], $user['role'], 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 getHomeSpace()
|
|
{
|
|
// TODO: Need to implement. Requires fleshing out additional space types.
|
|
return Space::retrieveFromDB(1, 1);
|
|
}
|
|
|
|
public function getSpaces()
|
|
{
|
|
$stmt = 'SELECT space FROM spaceMembers WHERE user = ?';
|
|
$rawSpaces = exec_stmt($stmt, 'i', $this->id);
|
|
$spaces = [];
|
|
while ($r = $rawSpaces->fetch_assoc()) {
|
|
$space = Space::retrieveFromDB($r['space']);
|
|
if (!$space->getParentSpace())
|
|
$spaces[] = $space;
|
|
}
|
|
|
|
return $spaces;
|
|
}
|
|
|
|
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 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->id);
|
|
$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->id);
|
|
$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->id);
|
|
$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->id);
|
|
$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->id, $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->id);
|
|
$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->id);
|
|
$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 [];
|
|
}
|
|
}
|
|
}
|
|
|
|
class Creator extends User {}
|