<?php namespace FaZeBook; use FaZeBook\Data\Activity; use FaZeBook\Data\Message; use FaZeBook\Data\Post; use FaZeBook\Data\User; use PDO; use R; class Data { private static $instance; public static function getInstance() { if(is_null(self::$instance)) { self::$instance = new Data(self::DB_FILE); } return self::$instance; } private static $testingInstance; public static function getTestingInstance() { if(is_null(self::$testingInstance)) { if(file_exists(self::DB_FILE_TEST)) { unlink(self::DB_FILE_TEST); } self::$testingInstance = new Data(self::DB_FILE_TEST); } return self::$testingInstance; } const DATA_DIR = "../data"; const DB_FILE = self::DATA_DIR . "/db.sqlite"; const DB_FILE_TEST = self::DATA_DIR . "/test.sqlite"; const SQL_INIT_FILE = "../sql/init.sql"; private function __construct(string $dbFile) { if(!is_dir(self::DATA_DIR)) { mkdir(self::DATA_DIR); } if(!file_exists($dbFile)) { // RedBean does not like uppercase chars in column names. Does not matter, SQL is case insensitive $init_sql = strtolower(file_get_contents(self::SQL_INIT_FILE)); $pdo = new PDO('sqlite:' . $dbFile); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec($init_sql); $pdo = null; // closes the connection } R::setup('sqlite:' . $dbFile); R::freeze(TRUE); // prevents RedBeanPHP from modifying the database schema R::setAutoResolve(TRUE); } /* ********** Creation / Deletion / Basic queries ********** */ /* ----- User ----- */ public function addUser(string $firstName, string $lastName, string $email, string $password): User { $dbo = R::dispense('user'); $dbo->firstName = $firstName; $dbo->lastName = $lastName; $dbo->email = $email; $dbo->passwordHash = ""; $dbo->lastSeen = time(); R::store($dbo); $user = new User($dbo); $user->setPassword($password); $this->addActivity($firstName . " " . $lastName . " hat sich beim FaZeBook angemeldet!"); return $user; } public function getUser(int $userId) { $dbo = R::load('user', $userId); return $dbo->id == 0 ? null : new User($dbo); } public function getRandomUsers($num){ $dbos = R::findAll('user', 'ORDER BY RANDOM() LIMIT ?', [$num]); return array_map(function($dbo) { return new User($dbo); }, $dbos); } public function getUserByEmail(string $email) { $dbo = R::findOne('user', 'email = ?', [ $email ]); return $dbo == null ? null : new User($dbo); } public function getUserByKarma(int $karma){ $dbo = R::findOne('user', 'karma = ?', [ $karma ]); return $dbo == null ? null : new User($dbo); } public function getUserAvatarLink(User $user): string { $avatarUrl = $user->getAvatarUrl(); if(is_null($avatarUrl)) { return "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Bundesarchiv_Bild_183-R68588,_Otto_von_Bismarck.jpg/220px-Bundesarchiv_Bild_183-R68588,_Otto_von_Bismarck.jpg"; } return $avatarUrl; } public function listUsers(): array { $dbos = R::findAll('user'); return array_map(function($dbo) { return new User($dbo); }, $dbos); } public function deleteUser(User $user) { R::exec('DELETE FROM Post WHERE Author_ID = :id OR Page_User_ID = :id', [':id' => $user->dbo->id]); R::exec('DELETE FROM Message WHERE Sender_ID = :id OR Recipient_ID = :id', [':id' => $user->dbo->id]); R::exec('DELETE FROM Friend WHERE Friend_A_ID = :id OR Friend_B_ID = :id', [":id" => $user->dbo->id]); R::exec('DELETE FROM Like WHERE Liker_ID = :id OR Liked_ID = :id', [":id" => $user->dbo->id]); R::trash($user->dbo); } /* ----- Post ----- */ public function addPost(User $author, User $pageUser, bool $gossip, int $type, string $content): Post { $dbo = R::dispense('post'); $dbo->author = $author->dbo; $dbo->page_user = $pageUser->dbo; $dbo->gossip = $gossip; $dbo->type = $type; $dbo->content = $content; $dbo->time = time(); R::store($dbo); $post = new Post($dbo); if (!$gossip) $this->addActivity($author->getFullName() . " hat einen Beitrag auf dem Profil von " . $pageUser->getFullName() . " verfasst!"); return $post; } private function listPosts(User $user, string $fieldName, bool $includeGossip): array { $condition = ' ' . $fieldName . ' = ?'; if(!$includeGossip) { $condition .= ' AND Gossip = 0'; } $condition .= ' ORDER BY Time DESC'; $dbos = R::find('post', $condition, [ $user->dbo->id ]); return array_map(function($dbo) { return new Post($dbo); }, $dbos); } public function listPagePosts(User $user, bool $includeGossip): array { return $this->listPosts($user, 'Page_User_ID', $includeGossip); } public function listAuthoredPosts(User $author): array { return $this->listPosts($author, 'Author_ID', true); } public function deletePost(Post $post) { $this->addActivity("Auf dem Profil von " . $post->getPageUser()->getFullName() . " wurde ein Post von " . $post->getAuthor()->getFullName() . " gelöscht!"); R::trash($post->dbo); } public function countPostsAuthor($userID) : int{ return R::count('post', 'author_id = ?', [$userID]); } public function countPostsAuthorByType($userID, $type) : int{ return R::count('post', 'author_id = :id AND type = :tt', [":id" => $userID, ":tt" => $type]); } public function countPostsAuthorGossip($userID) : int{ return R::count('post', 'author_id = ? AND gossip != 0', [$userID]); } public function countPostsReceived($userID) : int{ return R::count('post', 'page_user_id = ?', [$userID]); } public function countPostsReceivedByType($userID, $type) : int{ return R::count('post', 'page_user_id = :id AND type = :tt', [":id" => $userID, ":tt" => $type]); } public function countPostsReceivedGossip($userID) : int{ return R::count('post', 'page_user_id = ? AND gossip != 0', [$userID]); } public function getRandomGossip(){ $dbo = R::findOne('post', 'WHERE gossip != 0 ORDER BY RANDOM() LIMIT 1'); return $dbo == null ? null : new Post($dbo); } /* ----- Message ----- */ public function addMessage(User $sender, User $recipient, string $content): Message { $dbo = R::dispense('message'); $dbo->sender = $sender->dbo; $dbo->recipient = $recipient->dbo; $dbo->time = time(); $dbo->content = $content; $dbo->read = false; R::store($dbo); $message = new Message($dbo); return $message; } public function listMessagesBetween(User $userA, User $userB): array { $condition = ' (Sender_ID = :userA AND Recipient_ID = :userB)' . ' OR (Sender_ID = :userB AND Recipient_ID = :userA)' . ' ORDER BY Time ASC'; $dbos = R::find('message', $condition, [ "userA" => $userA->dbo->id, "userB" => $userB->dbo->id ]); return array_map(function($dbo) { return new Message($dbo); }, $dbos); } public function countUnreadMessagesBetween(User $recipient, User $sender): int { $condition = ' (Sender_ID = :sender AND Recipient_ID = :recipient)' . ' AND (NOT read)'; return R::count('message', $condition, [ "recipient" => $recipient->dbo->id, "sender" => $sender->dbo->id ]); } public function deleteMessage($message) { R::trash($message->dbo); } public function countMessagesAuthor($userID) : int{ return R::count('message', 'sender_id = ?', [$userID]); } public function countMessagesRecipient($userID) : int{ return R::count('message', 'recipient_id = ?', [$userID]); } /* ----- Friends ----- */ public function setFriend(User $userA, User $userB) { if($this->isFriend($userA, $userB)) { return; } $dbo = R::dispense('friend'); $dbo->friendA = $userA->dbo; $dbo->friendB = $userB->dbo; R::store($dbo); $this->addActivity($userA->getFullName() . " und " . $userB->getFullName() . " sind jetzt befreundet!"); } public function isFriend(User $userA, User $userB): bool { if($userA->getId() === $userB->getId()) { return true; } return R::count('friend', ' (Friend_A_ID = :userA AND Friend_B_ID = :userB)' . ' OR (Friend_A_ID = :userB AND Friend_B_ID = :userA)', [ "userA" => $userA->getId(), "userB" => $userB->getId()]) > 0; } public function listFriends(User $user): array { $condition = ' Id IN (' . ' SELECT Friend_A_ID FROM Friend WHERE Friend_B_ID = :user' . ' UNION' . ' SELECT Friend_B_ID FROM Friend WHERE Friend_A_ID = :user' . ')'; $dbos = R::find('user', $condition, [ "user" => $user->dbo->id ]); return array_map(function($dbo) { return new User($dbo); }, $dbos); } public function deleteFriend(User $userA, User $userB) { R::exec('DELETE FROM Friend WHERE (Friend_A_ID = :userA AND Friend_B_ID = :userB)' . ' OR (Friend_A_ID = :userB AND Friend_B_ID = :userA)', [ "userA" => $userA->getId(), "userB" => $userB->getId()]); $this->addActivity($userA->getFullName() . " und " . $userB->getFullName() . " sind jetzt keine Freunde mehr!"); } public function countFriends($userID){ return R::count('friend', 'friend_a_id = :id OR friend_b_id = :id', [':id' => $userID]); } /* ----- Likes ----- */ // memo: $user->dbo->alias('liker')->aggr('ownLikeList', 'liked', 'user'); // returns a list of all people the user liked public function setLike(User $liker, User $liked, int $likeType) { $dbo = R::dispense('like'); $dbo->liker = $liker->dbo; $dbo->liked = $liked->dbo; $dbo->type = $likeType; R::store($dbo); } public function getLike(User $liker, User $liked) { $res = R::getCell('SELECT Type FROM Like WHERE Liker_ID = ? AND Liked_ID = ?', [$liker->getId(), $liked->getId()]); return $res === null ? null : (int) $res; } public function deleteLike(User $liker, User $liked) { R::exec('DELETE FROM Like WHERE Liker_ID = ? AND Liked_ID = ?', [$liker->getId(), $liked->getId()]); } public function getMaxKarma() : int{ return (int) R::getCell('SELECT MAX (karma) FROM user'); } public function getMinKarma() : int{ return (int) R::getCell('SELECT MIN (karma) FROM user'); } public function countLikes($userID) : int{ return R::count('like', 'liked_id = ? AND type = ' . LikeType::LIKE, [$userID]); } public function countDislikes($userID) : int{ return R::count('like', 'liked_id = ? AND type = ' . LikeType::DISLIKE, [$userID]); } /* ----- Activity ----- */ public function addActivity(string $event): Activity { $dbo = R::dispense('activity'); $dbo->time = time(); $dbo->event = $event; R::store($dbo); $activity = new Activity($dbo); return $activity; } public function listActivity(int $limit) { $dbos = R::find('activity', " ORDER BY TIME DESC LIMIT ?", [ $limit ]); return array_map(function($dbo) { return new Activity($dbo); }, $dbos); } /* ********** Actions that modify more than one database row ********** */ public function quickScope(User $fragger, User $noob) { $fragger->dbo->kills++; $noob->dbo->deaths++; // TODO: Maybe log an event or something? idk } } ?>