From ae76b11a87519270a6ebb99a93c91a566ec27301 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Tue, 10 Jun 2025 10:46:22 -0600 Subject: [PATCH] likely the last commit had a serious vulnerability. this is just a sync --- director.php | 21 ++ functions/init.php | 8 + functions/post.php | 1 + functions/relationships/spaceMember.php | 41 +++ functions/space.php | 26 +- functions/todo.php | 48 +++ functions/user.php | 407 ++++++++---------------- spaces.php | 76 +++-- 8 files changed, 304 insertions(+), 324 deletions(-) create mode 100644 functions/relationships/spaceMember.php create mode 100644 functions/todo.php diff --git a/director.php b/director.php index 727cdf6..bdec902 100644 --- a/director.php +++ b/director.php @@ -39,6 +39,12 @@ if (isset($_POST['ajaxId'])) { case 'viewEssay': handleViewEssay(); break; + case 'viewTodo': + handleViewTodo(); + break; + case 'todoStatusChange': + handleTodoStatusChange(); + break; } } else if (isset($_POST['formID'])) { switch ($_POST['formID']) { @@ -64,6 +70,21 @@ if (isset($_POST['ajaxId'])) { header('Location: ' . $_POST['redirect']); } +function handleViewTodo() +{ + if (!isset($_POST['space']) || !is_int($_POST['space'])) + return; + + $user = unserialize($_SESSION['user']); + // TODO: Add checks here if the user is authorized. JWT stuff. +} + +function handleTodoStatusChange() +{ + if (!isset($_POST['id']) || !isset($_POST['status']) || !is_int($_POST['id']) || !is_string($_POST['status'])) + return; +} + function handleViewEssay() { if (!isset($_POST['id'])) diff --git a/functions/init.php b/functions/init.php index aef4482..8fb9f7c 100644 --- a/functions/init.php +++ b/functions/init.php @@ -16,6 +16,11 @@ require_once 'functions/space.php'; if (!isset($_SESSION['initialized'])) { $_SESSION['initialized'] = true; $_SESSION['theme'] = 'dark'; + if (isset($_SESSION['user'])) { + $tmp = unserialize($_SESSION['user'])->getSpaces(); + foreach ($tmp as $s) + $_SESSION['spaces'][] = serialize($s->getSpace()); + } } else if (isset($_POST['theme'])) { $_SESSION['theme'] = $_POST['theme']; unset($_POST['theme']); @@ -26,3 +31,6 @@ if (!isset($_SESSION['initialized'])) { define('owner', 1); define('developer', 2); define('user', 3); + +if (isset($_SESSION['user']) && !isset($GLOBALS['spaces'])) { +} diff --git a/functions/post.php b/functions/post.php index 1c3d2e4..a618d90 100644 --- a/functions/post.php +++ b/functions/post.php @@ -45,6 +45,7 @@ class Post public function __construct(int $space = 2025, ?int $id = null, User $creator, int $postType, int $status) { + error_log('Instantiating a post'); $this->space = $space; $this->id = $id; $this->creator = $creator; diff --git a/functions/relationships/spaceMember.php b/functions/relationships/spaceMember.php new file mode 100644 index 0000000..236f73e --- /dev/null +++ b/functions/relationships/spaceMember.php @@ -0,0 +1,41 @@ +getId()) || !Space::exists($space->getId())) + return; + + $this->user = $user; + $this->space = $space; + $this->role = $role; + $this->addedAt = $addedAt; + $this->favorited = $favorited; + + if ($id < 0) { + $this->id = $id * -1; + + $stmt = 'INSERT INTO spaceMembers (id, user, space, role, favorited) VALUES (?, ?, ?, ?, ?)'; + exec_stmt($stmt, 'iiiii', $this->id, $user->getId(), $space->getId(), $role, $favorited); + } + } + + public function getUser() + { + return $this->user; + } + + public function getSpace() + { + return $this->space; + } +} diff --git a/functions/space.php b/functions/space.php index ed8f338..0befdb7 100644 --- a/functions/space.php +++ b/functions/space.php @@ -10,16 +10,15 @@ class Space private string $name; private string $description; private int $visibility; - private ?array $members; private ?array $posts; private ?Space $parent; - public function __construct(int $id, string $name, string $description, int $visibility, ?array $members, ?array $posts, ?Space $parent = null, bool $favorited = false) + public function __construct(int $id, string $name, string $description, int $visibility, ?array $posts, ?Space $parent = null, bool $favorited = false) { + error_log('Constructing a space with id: ' . $id); $this->name = $name; $this->description = $description; $this->visibility = $visibility; - $this->members = $members; $this->posts[] = $posts; $this->parent = $parent; @@ -57,7 +56,7 @@ class Space public function getSubSpaces(): ?array { - $stmt = 'SELECT id FROM space WHERE parentSpace = ?'; + $stmt = 'SELECT id FROM space WHERE parent = ?'; $rawSubSpaces = exec_stmt($stmt, 'i', $this->id); $subSpaces = []; @@ -73,6 +72,11 @@ class Space return $this->parent; } + public function hasParent(): bool + { + return isset($this->parent); + } + public function getCardHTML(): string { $html = ' @@ -120,6 +124,7 @@ class Space public static function retrieveFromDB(int $space, int $user = -1): ?Space { + error_log('Retrieving a space from DB.'); if ($user != -1) { $stmt = 'SELECT * FROM spaceMembers WHERE space = ? AND user = ?'; $isMember = exec_stmt($stmt, 'ii', $space, $user)->fetch_assoc(); @@ -141,28 +146,17 @@ class Space else $parent = null; - $stmt = 'SELECT user FROM spaceMembers WHERE space = ?'; - $members = []; - $rawMembers = exec_stmt($stmt, 'i', $rawSpace['id']); - foreach ($rawMembers as $m) - $members[] = User::retrieveFromDB($m['user']); - $stmt = 'SELECT post FROM spacePosts INNER JOIN post ON spacePosts.post = post.id WHERE post.space = ? ORDER BY post.createdAt DESC'; $posts = []; $rawPosts = exec_stmt($stmt, 'i', $rawSpace['id']); while ($row = $rawPosts->fetch_assoc()) $posts[] = Post::retrieveFromDB($row['post']); - return new Space($rawSpace['id'], $rawSpace['name'], $rawSpace['description'], $rawSpace['visibility'], $members, $posts, $parent); + return new Space($rawSpace['id'], $rawSpace['name'], $rawSpace['description'], $rawSpace['visibility'], $posts, $parent); } else return null; } - public function allowUser(User $user) - { - return in_array($user, $this->members); - } - public function getPosts() { return $this->posts; diff --git a/functions/todo.php b/functions/todo.php new file mode 100644 index 0000000..9d58f2d --- /dev/null +++ b/functions/todo.php @@ -0,0 +1,48 @@ +title = $title; + $this->description = $description; + $this->due = $due; + $this->priority = $priority; + $this->link = $link; + $this->createdAt = $createdAt; + + if ($id < 0) { + $this->id = $id * -1; + $stmt = 'INSERT INTO todo (id, space, title, description, due, priority, link) VALUES (?, ?, ?, ?, ?, ?, ?)'; + exec_stmt($stmt, 'iisssiss', $this->id, $space, $title, $description, $due, $priority, $link); + } else + $this->id = $id; + } + + public function getHTML() + { + $html = ' +
+ +
+ '; + + return $html; + } +} diff --git a/functions/user.php b/functions/user.php index 999e565..257e06d 100644 --- a/functions/user.php +++ b/functions/user.php @@ -1,4 +1,5 @@ website = $website; $this->isActive = false; $this->role = $role; - $this->spaces = null; + $this->spaces = $spaces; + $this->passwordHash = $passwordHash; if ($id < 0) { $this->id = $id * -1; @@ -66,7 +70,8 @@ class User 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); + $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; } @@ -98,15 +103,132 @@ class User return true; } - public static function retrieveFromDB(int $userId): ?User + public function add(SpaceMember $sm) { - $stmt = 'SELECT id, username, profilePicture, bio, website, role FROM user WHERE id = ?'; - $user = exec_stmt($stmt, 'i', $userId)->fetch_assoc(); + $this->spaces[] = $sm; + } - if ($user) - return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website'], $user['role'], null); - else + 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() @@ -190,271 +312,4 @@ class User 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 {} diff --git a/spaces.php b/spaces.php index 8712ef9..2756b32 100644 --- a/spaces.php +++ b/spaces.php @@ -23,23 +23,25 @@ if (isset($_GET['view'])) { case 'show': // TODO: After implementing the notification modal, notify the user on success/fail auth events. if (isset($_GET['id'])) { - $space = Space::retrieveFromDB($_GET['id']); - if ($space) { - if (isset($_SESSION['user'])) - if ($space->allowUser(unserialize($_SESSION['user']))) + if (isset($_SESSION['user'])) { + $availableSpaces = unserialize($_SESSION['user'])->getSpaces(); + foreach ($availableSpaces as $s) { + $space = $s->getSpace(); + if ($space->getId() == $_GET['id']) { echo displaySpace($_GET['id']); - else if ($space->getVisibility() == 1) + break; + } else if ($space->getVisibility() == 1) { echo displaySpace($_GET['id']); - else + break; + } else echo 'TODO: Notification Modal -> User not a member'; - else { - if ($space->getVisibility() == 1) - echo displaySpace($_GET['id']); - else - echo 'TODO: Notification Modal -> Space is not Open'; } - } else - echo errorScreen('Space does not exist!', 'The Space ID provided does not exist in the database.'); + } else { + if ($space->getVisibility() == 1) + echo displaySpace($_GET['id']); + else + echo 'TODO: Notification Modal -> Space is not Open'; + } } else echo displaySpaceList(); break; @@ -49,7 +51,6 @@ if (isset($_GET['view'])) { else echo displaySpace(2025); break; - default: echo displaySpaceList(); break; @@ -134,22 +135,25 @@ function displayEssay(Essay $e) function displaySpaceList() { + if (!isset($_SESSION['user'])) + return displaySpace(); + $user = unserialize($_SESSION['user']); $html = ' -
-
-

Spaces

-
-
+
+
+

Spaces

+
+
'; $spaces = $user->getSpaces(); foreach ($spaces as $s) - $html .= $s->getCardHTML(); + $html .= $s->getSpace()->getCardHTML(); $html .= ' -
+
'; return $html; @@ -157,15 +161,25 @@ function displaySpaceList() function displaySpace($id = 2025) { - $space = Space::retrieveFromDB($id); + $spaces = unserialize($_SESSION['user'])->getSpaces(); + foreach ($spaces as $s) { + $space = $s->getSpace(); + if ($space->getId() == $id) { + break; + } + } + + if (!$space) + return; + if ($space) { $html = ' -
-
-

' . $space->getName() . '

-

' . $space->getDescription() . '

-
-
+
+
+

' . $space->getName() . '

+

' . $space->getDescription() . '

+
+
'; $subspaces = $space->getSubSpaces(); @@ -175,11 +189,9 @@ function displaySpace($id = 2025) // TODO: Need to investigate this bug in Posts $posts = $space->getPosts(); - foreach ($posts as $post) { - foreach ($post as $p) { + foreach ($posts as $post) + foreach ($post as $p) $html .= $p->getCardHTML(); - } - } $html .= '