From b01c5011ed0b31d635a0d2ca825222c2400ec6ed Mon Sep 17 00:00:00 2001 From: Epagris Date: Tue, 17 Sep 2024 16:47:47 +0200 Subject: [PATCH] - GameMgr OO implementation --- class/GameMgr.php | 398 ++++++++++++++++++++++++++++++++++++++++++++- class/GroupMgr.php | 65 +++++--- class/UserMgr.php | 17 +- 3 files changed, 446 insertions(+), 34 deletions(-) diff --git a/class/GameMgr.php b/class/GameMgr.php index 10030c1..bd45327 100644 --- a/class/GameMgr.php +++ b/class/GameMgr.php @@ -1,13 +1,407 @@ false, // player may traverse back and forth between challenges + "time_limit" => 0, // no time limit; otherwise, this field indicates time limit in seconds + "repeatable" => false // this test can be taken multiple times + ]; + + public const CURRENT_GAME_VERSION = 2; // MUST BE INCREMENTED!! + + // -------- + private int $_id; // Game's ID private string $name; // Game's name private string $owner; // Game's owner private array $contributors; // Contributors to the game private string $description; // Game's description - private bool $gameFileIsPresent; // indicates if game CSV is in place + private bool $gameFileIsPresent; // Indicates if game CSV is in place + private array $properties; // Collection of several game properties + private bool $public; // Is this game publicly available? + private string $publicId; // Public-accessible ID + private int $VERSION; // Game representation version (used during updates) + private GameMgr $gameMgr; // Game manager managing this instance + private array $challenges; // Challenges + + // ------- + + static private function patchUpGameDate(array &$a) : void + { + $version = $a["version"] ?? 0; + if ($version < 2) { // update to game version 2 + if (!key_exists("public_id", $a)) { + $a["public"] = false; + $a["public_id"] = generate_public_id(); + } + + $a["version"] = 2; + } + } + + // Store modifications. + function storeMods(): void + { + $this->patchUpGameDate(); + $this->gameMgr->updateGame($this); + } + + // Load game challenges. + public function loadChallenges(): void + { + if ($this->isGameFileIsPresent()) { // load if file is present + $this->challenges = json_decode(file_get_contents($this->getGameFile()), true); + } + } + + // Save challenges. + public function saveChallenges(): void + { + file_put_contents($this->getGameFile(), json_encode($this->challenges)); // store challenges in JSON-format + } + + // ------- + + function __construct(GameMgr &$gameMgr, string $name, string $description = "", int $id = -1, string $owner = "", + array $contributors = [], bool $gameFileIsPresent = false, array $properties = [], + bool $public = false, string $publicId = "", int $version = 2) + { + $this->gameMgr = $gameMgr; + $this->id = $id; + $this->name = $name; + $this->description = $description; + $this->owner = $owner; + $this->contributors = $contributors; + $this->gameFileIsPresent = $gameFileIsPresent; + $this->properties = $properties; + $this->public = $public; + $this->publicId = $publicId; + $this->VERSION = $version; + $this->challenges = []; + } + + // Create game from array representation. + static function fromArray(GameMgr &$gameMgr, array $a): Game + { + $id = $a["id"] ?? -1; + self::patchUpGameDate($a); + return new Game($gameMgr, $a["name"], $a["description"], $id, $a["owner"], $a["contributors"], + $a["gameFileIsPresent"], $a["properties"], $a["public"], $a["publicId"], $a["version"]); + } + + // Convert game to array representation. + function toArray(array $omit = []): array + { + $a = [ + "_id" => $this->_id, + "name" => $this->name, + "description" => $this->description, + "owner" => $this->owner, + "contributors" => $this->contributors, + "game_file_present" => $this->gameFileIsPresent, + "properties" => $this->properties, + "public" => $this->public, + "public_id" => $this->publicId, + "version" => $this->VERSION, + ]; + + foreach ($omit as $field) { + unset($a[$field]); + } + + return $a; + } + + // Export challenges to a CSV file. + function exportChallengesToCSV(&$f): void + { + // load challenges + $this->loadChallenges(); + + // populate CSV file + foreach ($this->challenges as $ch) { + $csvline = [ + $ch["question"], + $ch["image_url"], + ]; + $csvline = array_merge($csvline, $ch["answers"]); + fputcsv($f, $csvline); + } + } + + // Get game directory NAME with path. Does not check if the game directory exists or not. + function getGameDir(): string + { + return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId(); + } + + // Get game file NAME with path. Does not check whether the game file is in place or not. + function getGameFile(): string + { + return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId() . DIRECTORY_SEPARATOR . GAME_FILE; + } + + // Is the given user the owner of the game? + function isUserOwner(string $nickname): bool + { + return $this->owner === $nickname; + } + + // Is the given user a contributor of the game? + function isUserContributor(string $nickname): bool + { + return in_array($nickname, $this->contributors); + } + + // Is user contributor or owner? + function isUserContributorOrOwner(string $nickname): bool + { + return $this->isUserContributor($nickname) || $this->isUserOwner($nickname); + } + + const CSV_ENCODINGS = ["UTF-8", "Windows-1252"]; + + // Import challenges from a CSV table. + function importChallengesFromCSV(string $csv_path): array + { + // convert text encoding into UTF-8 + $data = file_get_contents($csv_path); + $encoding = "UNKNOWN"; + foreach (self::CSV_ENCODINGS as $enc) { // detect encoding + if (mb_check_encoding($data, $enc)) { + $encoding = $enc; + break; + } + } + + if ($encoding !== "UNKNOWN") { // if encoding has been detected successfully + $data = mb_convert_encoding($data, "UTF-8", $encoding); + file_put_contents($csv_path, $data); + } + + // clear challenges + $this->challenges = []; + + // load filled CSV file + $f = fopen($csv_path, "r"); + if (!$f) { // failed to open file + return ["n" => 0, "encoding" => $encoding]; + } + while ($csvline = fgetcsv($f)) { + // skip empty lines + if (trim(implode("", $csvline)) === "") { + continue; + } + if (count($csvline) >= 3) { + // construct challenge record + $ch = [ + "question" => trim($csvline[0]), + "image_url" => trim($csvline[1]), + "correct_answer" => trim($csvline[2]), + "answers" => array_filter(array_slice($csvline, 2), function ($v) { + return trim($v) !== ""; + }) + ]; + + // if image is attached to the challenge, then give a random name to the image + if ($ch["image_url"] !== "") { + $old_img_name = $ch["image_url"]; + $ext = pathinfo($old_img_name, PATHINFO_EXTENSION); + $ext = ($ext !== "") ? ("." . $ext) : $ext; + $new_img_name = uniqid("img_", true) . $ext; + $ch["image_url"] = $new_img_name; + + // rename the actual file + $old_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $old_img_name; + $new_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $new_img_name; + rename($old_img_path, $new_img_path); + } + + // store the challenge + $this->challenges[] = $ch; + } + } + fclose($f); + + // save challenges + $this->saveChallenges(); + + // update game with game file present + $this->gameFileIsPresent = true; + + // store modifications + $this->storeMods(); + + return ["n" => count($this->challenges), "encoding" => $encoding]; + } + + // --------- + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getOwner(): string + { + return $this->owner; + } + + public function setOwner(string $owner): void + { + $this->owner = $owner; + } + + public function getContributors(): array + { + return $this->contributors; + } + + public function setContributors(array $contributors): void + { + $this->contributors = $contributors; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function getId(): int + { + return $this->_id; + } + + public function isGameFileIsPresent(): bool + { + return $this->gameFileIsPresent; + } + + public function& getProperties(): array + { + return $this->properties; + } + + public function isPublic(): bool + { + return $this->public; + } + + public function getPublicId(): string + { + return $this->publicId; + } + + public function getChallenges(): array + { + return $this->challenges; + } } + class GameMgr { + private \SleekDB\Store $db; // game database + // -------- + + static private function genPublicId(): string + { + return uniqid("p"); + } + + // -------- + + function __construct() + { + $this->db = new \SleekDB\Store(GAMEDB, DATADIR, ["timeout" => false]); + } + + // Get game by ID. + function getGame(string $gameid): Game|null + { + $game_data_array = $this->db->findById($gameid); + return count($game_data_array) != 0 ? Game::fromArray($this, $game_data_array[0]) : null; + } + + // Get public game. FIXME!!! + function getPublicGame(string $public_id): Game|null + { + $game_data_array = $this->db->findBy([["public", "=", "true"], "AND", ["public_id", "=", $public_id]]); + return count($game_data_array) != 0 ? Game::fromArray($this, $game_data_array[0]) : null; + } + + // Update game. + function updateGame(Game $game): void + { + $a = $game->toArray(); + $this->db->update($a); + } + + function addGame(string $name, string $owner, string $description, array $properties = Game::DEFAULT_GAME_PROPERTIES, + array $contributors = [], array $challenges = []): bool + { + $game_data = [ + "name" => $name, + "owner" => $owner, + "contributors" => $contributors, + "description" => $description, + "game_file_present" => false, + "properties" => $properties, + "groups" => [], + "public" => false, + "public_id" => self::genPublicId(), + "version" => Game::CURRENT_GAME_VERSION + ]; + + $game_data = $this->db->insert($game_data); + + // prepare game context + $game = Game::fromArray($this, $game_data); + $current_game_media_dir = $game->getGameDir(); + mkdir($current_game_media_dir); + $game->saveChallenges(); + return true; + } + + // Delete game by ID. + function deleteGame(string $gameid): void + { + $this->db->deleteById($gameid); + } + + // Get all game data by contributor nickname. + function getAllGameDataByContributor(string $nickname): array { + $games = []; + if ($nickname !== "*") { + $game_data_array = $this->db->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]); + } else { + $game_data_array = $this->db->findAll(); + } + foreach ($game_data_array as $game_data) { + $games[] = Game::fromArray($this, $game_data); + } + return $games; + } + + // Get all games. + function getAllGames() : array { + $gamesa = $this->db->findAll(); + $games = []; + foreach ($gamesa as $a) { + $games[] = new Game($this, $a); + } + return $games; + } + + // ------- } \ No newline at end of file diff --git a/class/GroupMgr.php b/class/GroupMgr.php index 3ddad39..2d2b163 100644 --- a/class/GroupMgr.php +++ b/class/GroupMgr.php @@ -63,79 +63,91 @@ class Group } // Get group's ID. - function getID() : int + function getID(): int { return $this->_id; } // Get group's name. - function getName() : string + function getName(): string { return $this->name; } // Set group's name. - function setName(string $name) : void { + function setName(string $name): void + { $this->name = $name; $this->storeMods(); } // Tell if group is unique - function isUnique() : bool { + function isUnique(): bool + { return $this->unique; } // Get group's description. - function getDescription() : string { + function getDescription(): string + { return $this->description; } // Set group's description. - function setDescription(string $description) : void { + function setDescription(string $description): void + { $this->description = $description; $this->storeMods(); } // Get group's owner. - function getOwner() : string { + function getOwner(): string + { return $this->owner; } // Set group's owner. - function setOwner(string $owner) : void { + function setOwner(string $owner): void + { $this->owner = $owner; $this->storeMods(); } // Get list of editors. - function getEditors() : array { + function getEditors(): array + { return $this->editors; } // Set editors. - function setEditors(array $editors) : void { + function setEditors(array $editors): void + { $this->editors = $editors; $this->storeMods(); } // Get group members. - function getMembers() : array { + function getMembers(): array + { return $this->members; } // Set group members. - function setMembers(array $members) : void { + function setMembers(array $members): void + { $this->members = $members; $this->storeMods(); } // Get games. - function getGames() : array { + function getGames(): array + { return $this->games; } // Set games. - function setGames(array $games) : void { + function setGames(array $games): void + { $this->games = $games; $this->storeMods(); } @@ -168,12 +180,14 @@ class Group } // Returns whether the user is an editor of this group. - function isUserEditor(string $nickname): bool { + function isUserEditor(string $nickname): bool + { return in_array($nickname, $this->editors); } // Returns whether the user is an editor or the owner of the group. - function isUserContributor(string $nickname): bool { + function isUserContributor(string $nickname): bool + { return $this->isUserEditor($nickname) || ($this->owner === $nickname); } @@ -190,7 +204,7 @@ class Group } // Get groups unique name. - function getUniqueName() : string + function getUniqueName(): string { return $this->name . ($this->unique ? "" : ("#" . $this->_id)); } @@ -202,7 +216,8 @@ class GroupMgr // ------------------------- - private function manageTwins(int $current_group_id, string $groupname): bool { + private function manageTwins(int $current_group_id, string $groupname): bool + { // make test on name uniqueness $twins = $this->db->findBy([["groupname", "=", "$groupname"], "AND", ["_id", "!=", $current_group_id]]); $unique = count($twins) == 0; @@ -235,7 +250,8 @@ class GroupMgr } // Add a new group. - function addGroup(string $groupname, string $owner, string $description = ""): bool { + function addGroup(string $groupname, string $owner, string $description = ""): bool + { // test name uniqueness $unique = $this->manageTwins(0, $groupname); @@ -259,10 +275,10 @@ class GroupMgr // Delete group. function deleteGroup(string $groupid): void { - $group = $this->getGroup($groupid); - if ($group != null) { - $this->db->deleteById($groupid); - } + //$group = $this->getGroup($groupid); + //if ($group != null) { + $this->db->deleteById($groupid); + //} } // Get all groups. @@ -336,7 +352,8 @@ class GroupMgr private array $groupid_cache = []; // Convert group IDs into unique group names IN PLACE! - function resolveGroupIds(array &$groupids): void { + function resolveGroupIds(array &$groupids): void + { foreach ($groupids as &$groupid) { if (array_key_exists($groupid, $this->groupid_cache)) { $group = $this->getGroup($groupid); // fetch group diff --git a/class/UserMgr.php b/class/UserMgr.php index 46a640f..2c98b9d 100644 --- a/class/UserMgr.php +++ b/class/UserMgr.php @@ -125,7 +125,8 @@ class User } // Has the user quizmaster privileges? - function hasQuizmasterPrivilege(): bool { + function hasQuizmasterPrivilege(): bool + { return $this->privilege == PRIVILEGE_QUIZMASTER; } } @@ -196,13 +197,13 @@ class UserMgr return; } - $user = $this->getUser($nickname); - if ($user !== null) { - foreach ($user->getGroups() as $groupid) { - change_group_user_assignments($groupid, null, $nickname); - } - $this->db->deleteBy(["nickname", "=", $nickname]); - } +// $user = $this->getUser($nickname); +// if ($user !== null) { +// foreach ($user->getGroups() as $groupid) { +// change_group_user_assignments($groupid, null, $nickname); +// } + $this->db->deleteBy(["nickname", "=", $nickname]); + //} } // Dump all users. Users come wrapped in User objects.