id = $id; $this->username = $username; $this->status = $status; $this->email = $email; $this->image = $image; $this->isAdmin = $isAdmin; $this->memberSince = new DateTime($timestamp); $this->postCount = $postCount; } private static function getByConfirmCode(string $code): User { $db = Database::getInstance(); $stmt = $db->prepare( "SELECT b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b WHERE confirmationcode = :COD" ); $stmt->bindValue(":COD", $code); $stmt->execute(); $data = $stmt->fetch(); if (!$data) throw new Exception("NotFound"); return new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]); } /* * Statics */ public static function getByID(int $id): User { $db = Database::getInstance(); $stmt = $db->prepare( "SELECT b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b WHERE b.id = :ID" ); $stmt->bindValue(":ID", $id); $stmt->execute(); $data = $stmt->fetch(); if (!$data) throw new Exception("NotFound"); return new User($id, $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]); } public static function getByEmail(string $email): User { $db = Database::getInstance(); $stmt = $db->prepare( "SELECT b.id, b.benutzer, b.status, b.image, b.isadmin, b.zeitstempel, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b WHERE email = :EMAIL" ); $stmt->bindValue(":EMAIL", $email); $stmt->execute(); $data = $stmt->fetch(); if (!$data) throw new Exception("NotFound"); return new User($data["id"], $data["benutzer"], $data["status"], $email, $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]); } public static function getByToken(string $token): User { $db = Database::getInstance(); $stmt = $db->prepare( "SELECT b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b WHERE token = :TOKEN" ); $stmt->bindValue(":TOKEN", $token); $stmt->execute(); $data = $stmt->fetch(); if (!$data) throw new Exception("NotFound"); return new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]); } public static function logIn(string $email, string $password): array { $db = Database::getInstance(); // Get user data $stmt = $db->prepare( "SELECT b.*, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b WHERE email LIKE :EMAIL AND status = 1" ); $stmt->bindValue(":EMAIL", $email); $stmt->execute(); $data = $stmt->fetch(); if ($data) { $user = new User($data["id"], $data["benutzer"], $data["status"], $email, $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]); if (password_verify($password, $data["passwort"])) { // REHASH for safety should it somehow change if (password_needs_rehash($data["passwort"], PASSWORD_DEFAULT)) { $newHash = password_hash($password, PASSWORD_DEFAULT); $stmt = $db->prepare("UPDATE egb_benutzer SET passwort = :PAS WHERE id = :ID"); $stmt->bindValue(":PAS", $newHash); $stmt->bindValue(":ID", $user->getID()); $stmt->execute(); } // Generate token $stmt = $db->prepare("UPDATE egb_benutzer SET token = UUID() WHERE id = :ID"); $stmt->bindValue(":ID", $user->getID()); $stmt->execute(); // Get token $stmt = $db->prepare("SELECT token FROM egb_benutzer WHERE id = :ID"); $stmt->bindValue(":ID", $user->getID()); $stmt->execute(); $token = $stmt->fetch(PDO::FETCH_COLUMN, 0); // Return user and token if ($token) { return ["user" => $user, "token" => $token]; } // Token generation failed throw new Exception("Failed"); } else { // PW wrong throw new Exception("Invalid"); } } else { // User does not exist throw new Exception("NotFound"); } } public static function create(string $username, string $email, string $password): User { $db = Database::getInstance(); $guid = GUID::v4(); $stmt = $db->prepare( "INSERT INTO egb_benutzer(benutzer, passwort, email, confirmationcode) VALUES(:USR, :PAS, :EMA, :COD)" ); $stmt->bindValue(":USR", htmlspecialchars($username)); $stmt->bindValue(":PAS", password_hash($password, PASSWORD_DEFAULT)); $stmt->bindValue(":EMA", $email); $stmt->bindValue(":COD", $guid); try { $stmt->execute(); $user = User::getByID($db->lastInsertId()); mail( $email, "Account activation GuestBookDB", "Hello $username. To activate your account, visit https://khofmann.userpage.fu-berlin.de/phpCourse/exam/confirm?c=$guid" ); return $user; } catch (Exception $err) { if ($err->getCode() === "23000") throw new Exception("Duplicate"); throw $err; } } public static function confirm(string $confirmCode): User { $db = Database::getInstance(); $user = User::getByConfirmCode($confirmCode); $stmt = $db->prepare( "UPDATE egb_benutzer SET status = 1, confirmationcode = NULL WHERE id = :UID" ); $stmt->bindValue(":UID", $user->getID()); return User::getByID($user->getID()); } public static function list(int $page, int $limit) { $db = Database::getInstance(); $stmt = $db->prepare( "SELECT COUNT(*) FROM egb_gaestebuch" ); $stmt->execute(); $count = $stmt->fetch(PDO::FETCH_COLUMN, 0); $stmt = $db->prepare( "SELECT b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel, (SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount FROM egb_benutzer AS b" ); $stmt->execute(); $data = $stmt->fetchAll(); $list = array_map( fn ($item) => new User($item["id"], $item["benutzer"], $item["status"], $item["email"], $item["zeitstempel"], $item["image"], $item["isadmin"] === 1, $item["postCount"]), $data ); return ["pages" => intdiv($count, $limit + 1), "data" => $list]; } /* * Members */ public function logOut(): bool { $db = Database::getInstance(); $stmt = $db->prepare("UPDATE egb_benutzer SET token = NULL WHERE id = :ID"); $stmt->bindValue(":ID", $this->id); return $stmt->execute(); } public function update(?string $username, ?string $password, ?string $email): User { $db = Database::getInstance(); $db->beginTransaction(); $failed = []; $reasons = []; if (!empty($username)) { $stmt = $db->prepare("UPDATE egb_benutzer SET benutzer = :USR WHERE id = :ID"); $stmt->bindValue(":USR", htmlspecialchars($username)); $stmt->bindValue(":ID", $this->id); try { if (!$stmt->execute()) { array_push($failed, "username"); array_push($reasons, "generic"); } } catch (Exception $e) { array_push($failed, "username"); if ($e->getCode() === "23000") { $pdoErr = $stmt->errorInfo()[1]; if ($pdoErr === 1062) array_push($reasons, "Duplicate"); else array_push($reasons, "SQL: $pdoErr"); } else array_push($reasons, "{$e->getCode()}"); } } if (!empty($password)) { $stmt = $db->prepare("UPDATE egb_benutzer SET passwort = :PAS WHERE id = :ID"); $stmt->bindValue(":PAS", password_hash($password, PASSWORD_DEFAULT)); $stmt->bindValue(":ID", $this->id); try { if (!$stmt->execute()) { array_push($failed, "password"); array_push($reasons, "generic"); } } catch (Exception $e) { array_push($failed, "password"); if ($e->getCode() === "23000") { $pdoErr = $stmt->errorInfo()[1]; if ($pdoErr === 1062) array_push($reasons, "Duplicate"); else array_push($reasons, "SQL: $pdoErr"); } else array_push($reasons, "{$e->getCode()}"); } } if (!empty($email)) { // $destinationFilename = sprintf('%s.%s', uniqid(), $image->getExtension()); // $image->move(Config::getStoragePath() . "profilbilder/$destinationFilename"); $stmt = $db->prepare("UPDATE egb_benutzer SET email = :EMA WHERE id = :ID"); $stmt->bindValue(":EMA", $email); $stmt->bindValue(":ID", $this->id); try { if (!$stmt->execute()) { array_push($failed, "email"); array_push($reasons, "generic"); } } catch (Exception $e) { array_push($failed, "email"); if ($e->getCode() === "23000") { $pdoErr = $stmt->errorInfo()[1]; if ($pdoErr === 1062) array_push($reasons, "Duplicate"); else array_push($reasons, "SQL: $pdoErr"); } else array_push($reasons, "{$e->getCode()}"); } } if (count($failed) > 0) { $db->rollBack(); throw ApiError::failedUpdate($failed, $reasons); } $db->commit(); return User::getByID($this->id); } public function delete(): User { $db = Database::getInstance(); $stmt = $db->prepare("DELETE FROM egb_benutzer WHERE id = :ID"); $stmt->bindValue(":ID", $this->id); return $this; } /* * Getters */ public function getID(): int { return $this->id; } public function getUsername(): string { return $this->username; } public function getStatus(): int { return $this->status; } public function getEmail(): string { return $this->email; } public function getImage(): ?string { return $this->image; } public function getIsAdmin(): bool { return $this->isAdmin; } public function getMemberSince(): DateTime { return $this->memberSince; } public function getPostCount(): int { return $this->postCount; } /* * JSON */ public function jsonSerialize(): array { return [ 'id' => $this->id, 'username' => $this->username, 'status' => $this->status, 'email' => htmlspecialchars($this->email), 'image' => $this->image, 'isAdmin' => $this->isAdmin, 'memberSince' => $this->memberSince, 'postCount' => $this->postCount, ]; } }