dump_actions(); } $rh->add("dump_actions", [], PRIVILEGE_QUIZMASTER, "dump_actions", RESP_PLAIN, "Dump all registered actions."); /* ------------ ACTIONS AVAILABLE WITHOUT LOGGING IN ---------- */ // login the user function login(ReqHandler &$rh, array $params): string { global $userMgr; global $user; $nickname = $params["nickname"]; $password = $params["password"]; $user = $userMgr->getUser($nickname); if (($user !== null) && $user->checkPassword($password)) { session_start(); $_SESSION["nickname"] = $nickname; $result = "OK"; } else { $result = "FAIL"; } return $result; } $rh->add("login", ["nickname", "password"], PRIVILEGE_NONE, "login", RESP_PLAIN, "Log in the user."); // exit script if there's no live session or nickname is missing or the referenced user is non-existent if ((session_status() != PHP_SESSION_ACTIVE) || (!isset($_SESSION["nickname"])) || ($userMgr->getUser($_SESSION["nickname"]) === null)) { goto process_and_print; } $user = $userMgr->getUser($_SESSION["nickname"]); $nickname = $user->getNickname(); $privilege = $user->getPrivilege(); $groupMgr = new GroupMgr(); $gameMgr = new GameMgr(); $testMgr = new TestMgr(); /* ---------- ACTIONS REQUIRING BEING LOGGED IN ------------ */ function logout(ReqHandler &$rh, array $params): string { $_SESSION = []; // clean up session data setcookie(SESSION_NAME, "", -1); // invalidate cookie return "OK"; } function get_user_info(ReqHandler &$rh, array $params): array { global $user; $user_data_filtered = $user->toArray(); unset($user_data_filtered["password"]); return $user_data_filtered; } function get_available_games(ReqHandler &$rh, array $params): array { global $user; global $groupMgr; global $gameMgr; $games_by_groups = []; $groupids = $groupMgr->getUserGroupIDs($user->getNickname()); foreach ($groupids as $groupid) { $group = $groupMgr->getGroup($groupid); $game_collection = [ "groupname" => $group->getName(), "description" => $group->getDescription(), "games" => [] ]; $gameids = $group->getGames(); foreach ($gameids as $gameid) { $game = $gameMgr->getGame($gameid); if ($game->isGameFileIsPresent()) { $a = $game->toArray(Game::OMIT_ADVANCED_FIELDS); $game_collection["games"][] = $a; } } $games_by_groups[] = $game_collection; } return $games_by_groups; } function start_or_continue_test(ReqHandler &$rh, array $params): string { global $user; global $gameMgr; global $testMgr; global $groupMgr; if ($groupMgr->doesUserAccessGame($params["gameid"], $user->getNickname())) { $game = $gameMgr->getGame($params["gameid"]); $test = $testMgr->addOrContinueTest($game, $user); return $test->getId(); } else { return -1; } } function get_results_overview(ReqHandler &$rh, array $params): array { global $user; global $testMgr; $concluded_tests = $testMgr->getConcludedTests($params["gameid"], $user->getNickname()); $overviews = []; foreach ($concluded_tests as $ct) { $overview = [ "testid" => $ct->getId(), "start_time" => $ct->getStartTime(), "end_time" => $ct->getEndTime(), ...($ct->getSummary()->toArray()) ]; $overviews[] = $overview; } return $overviews; } function change_password(ReqHandler &$rh, array $params): string { $oldpass = $params["oldpass"]; $newpass = $params["newpass"]; global $user; $success = $user->changePassword($newpass, $oldpass); return $success ? "OK" : "FAIL"; } $rh->add("logout", [], PRIVILEGE_PLAYER, "logout", RESP_PLAIN, "Log out the user."); $rh->add("change_password", ["oldpass", "newpass"], PRIVILEGE_PLAYER, "change_password", RESP_PLAIN, "Change users password."); $rh->add("get_user_info", [], PRIVILEGE_PLAYER, "get_user_info", RESP_JSON, "Get user information."); $rh->add("get_available_games", [], PRIVILEGE_PLAYER, "get_available_games", RESP_JSON, "Get available games to the player."); $rh->add("start_or_continue_test", ["gameid"], PRIVILEGE_PLAYER, "start_or_continue_test", RESP_PLAIN, "Start new or continue an ongoing test."); $rh->add("get_results_overview", ["gameid"], PRIVILEGE_PLAYER, "get_results_overview", RESP_JSON, "Get a quick overview of player's results of a single game."); // test-related queries function does_test_belong_to_user(Test &$test): bool { global $user; return $test->getNickname() === $user->getNickname(); } function is_test_access_approved(Test &$test): bool { global $user; global $gameMgr; $game = $gameMgr->getGame($test->getGameId()); return does_test_belong_to_user($test) || $game->isUserContributorOrOwner($user->getNickname()) || $user->hasQuizmasterPrivilege(); } function access_test_data(string $testid): Test|null { global $testMgr; $testid = trim($testid); $test = ($testid !== "") ? $testMgr->getTest($testid) : null; // fetch test data if ($test === null) { return null; } // check if access is approved to the specific test if (!is_test_access_approved($test)) { return null; } // update the test if timed // update_timed_tests([$test_data]); FIXME!!! return $test; } function exclude_correct_answers(array &$challenges): void { foreach ($challenges as &$challenge) { $challenge["correct_answer"] = -1; } } function get_player_test(ReqHandler &$rh, array $params): array { $result = []; $test = access_test_data($params["testid"]); if ($test !== null) { $test_data_with_current_time = $test->toArray(); if ($test->isOngoing()) { exclude_correct_answers($test_data_with_current_time["challenges"]); } $test_data_with_current_time["current_time"] = time(); $result = $test_data_with_current_time; } return $result; } function save_player_answer(ReqHandler &$rh, array $params): string { $test = access_test_data($params["testid"]); if ($test !== null) { $test->saveAnswer($params["challenge_index"], $params["answer_index"]); return "OK"; } else { return "FAIL"; } } function submit_test(ReqHandler &$rh, array $params): string { $test = access_test_data($params["testid"]); if ($test !== null) { $test->concludeTest(); return "OK"; } else { return "FAIL"; } } function patch_through_image(string $gameid, string $img_url) { global $gameMgr; $game_dir = $gameMgr->getGame($gameid)->getGameDir(); $image_fetch_url = $game_dir . DIRECTORY_SEPARATOR . $img_url; $img_fp = fopen($image_fetch_url, "r"); if ($img_fp === false) { $img_fp = fopen(MISSING_IMAGE_PLACEHOLDER, "r"); } fpassthru($img_fp); fclose($img_fp); } function get_image(ReqHandler &$rh, array $params): string { $img_url = trim($params["img_url"] ?? ""); if ($img_url !== "") { $gameid = $params["gameid"]; patch_through_image($gameid, $img_url); } return ""; } $rh->add("get_test", ["testid"], PRIVILEGE_PLAYER, "get_player_test", RESP_JSON, "Get player's test by ID."); $rh->add("save_answer", ["testid", "challenge_index", "answer_index"], PRIVILEGE_PLAYER, "save_player_answer", RESP_PLAIN, "Store player's answer."); $rh->add("submit_test", ["testid"], PRIVILEGE_PLAYER, "submit_test", RESP_PLAIN, "Finish player's test."); $rh->add("get_image", ["gameid", "img_url"], PRIVILEGE_PLAYER, "get_image", RESP_NONE, "Get image per game."); // execute query if user has the player privilege level if ($privilege === PRIVILEGE_PLAYER) { goto process_and_print; } /* --------------- CREATOR LEVEL ACTIONS ---------------- */ $requester_nickname = $user->hasQuizmasterPrivilege() ? "*" : $nickname; // "*" means every game function create_update_game(ReqHandler &$rh, array $params): array { global $user; global $nickname; global $gameMgr; $update = $params[ReqHandler::ACTION_KEY] === "update_game"; $data = json_decode($params["data"], true) ?? []; if (($data === []) || (trim($data["name"] ?? "") === "")) { // no further processing return []; // ~exit... } // fetch fields $gameid = $data["_id"]; $name = $data["name"]; $description = $data["description"]; $contributors = explode_list($data["contributors"] ?? ""); $owner = $update ? trim($data["owner"] ?? $nickname) : $nickname; $properties = $data["properties"] ?? []; // result $result = []; // create or update if (!$update) { // CREATE $gameMgr->addGame($name, $owner, $description); } else { // UPDATE $game = $gameMgr->getGame($gameid); // fetch game if (($game !== null) && $game->isUserContributorOrOwner($nickname) || $user->hasQuizmasterPrivilege()) { // disable autostoring $game->disableAutoStoring(); // update game header data $game->setName($name); $game->setDescription($description); if ($game->isUserOwner($nickname) || $user->hasQuizmasterPrivilege()) { $game->setOwner($owner); } $game->setContributors($contributors); $game->setProperties($properties); // process game public flag: a game might be only public if not being time-constrained and is allowed to be taken multiple times $public = ($properties["time_limit"] !== 0) || (!$properties["repeatable"]) ? false : $data["public"]; $game->setPublic($public); // store modifications $game->storeMods(); // re-enable auto-storing $game->enableAutoStoring(); // update game file if supplied if (isset($_FILES["game_file"])) { // decide weather it's a package or a plain table $file = $_FILES["game_file"]; $challenge_import_status = []; // determine MIME type $file_type = strtolower(pathinfo($file["name"], PATHINFO_EXTENSION)); if ($file_type === "zip") { // a package was uploaded $zip = new ZipArchive; if ($zip->open($file["tmp_name"])) { $game_dir = $game->getGameDir(); // get game directory //$game_files = glob($game_dir); // get list of existing game files // remove former files recursively $dir_iter = new RecursiveDirectoryIterator($game_dir, FilesystemIterator::SKIP_DOTS); foreach ($dir_iter as $dir_item) { $item_path = $dir_item->getPathname(); $dir_item->isDir() ? rmdir($item_path) : unlink($item_path); } // extract package contents to the game directory $zip->extractTo($game_dir . DIRECTORY_SEPARATOR); // search for the CSV table file $csv_files = glob($game_dir . DIRECTORY_SEPARATOR . "*.csv") ?? []; if (count($csv_files) > 0) { $challenge_import_status = $game->importChallengesFromCSV($csv_files[0]); } } } else if ($file_type === "csv") { // a plain table was uploaded $challenge_import_status = $game->importChallengesFromCSV($file["tmp_name"]); } $result = $challenge_import_status; } } } return $result; } function get_all_game_headers(ReqHandler &$rh, array $params): array { global $requester_nickname; global $gameMgr; $games = $gameMgr->getAllGameDataByContributor($requester_nickname); $a = []; foreach ($games as &$game) { $a[] = $game->toArray(); } return $a; } function get_challenges(ReqHandler &$rh, array $params): string { global $user; global $gameMgr; $gameid = $params["gameid"]; $game = $gameMgr->getGame($gameid); $result = ""; if (($game !== null) && ($user->hasQuizmasterPrivilege() || ($game->isUserContributorOrOwner($user->getNickname())))) { $result = file_get_contents($game->getGameFile()); } return $result; } function delete_games(ReqHandler &$rh, array $params): string { global $user; global $gameMgr; $gameids = explode_list(trim($params["ids"])); foreach ($gameids as $gameid) { $game = $gameMgr->getGame($gameid); if (($game !== null) && ($game->isUserOwner($user->getNickname()))) { // only the owner may delete a game $gameMgr->deleteGame($gameid); } } return "OK"; } function export_game_file_csv(ReqHandler &$rh, array $params): string { global $user; global $gameMgr; $gameid = trim($params["gameid"]); $game = $gameMgr->getGame($gameid); if (($game !== null) && ($game->isUserContributorOrOwner($user->getNickname()) || $user->hasQuizmasterPrivilege())) { $f = tmpfile(); header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename=\"challenges_$gameid.csv\"\r\n"); $game->exportChallengesToCSV($f); fseek($f, 0); fpassthru($f); } return ""; } function get_results_by_gameid(ReqHandler &$rh, array $params): array { global $gameMgr; global $testMgr; global $user; $gameid = trim($params["gameid"]); $filter = trim($params["filter"] ?? ""); $ordering = trim($params["orderby"] ?? ""); $groups = explode_list(trim($params["groups"] ?? "")); $game = $gameMgr->getGame($gameid); $result = []; if (($game !== null) && ($game->isUserContributorOrOwner($user->getNickname()) || $user->hasQuizmasterPrivilege())) { // creating filter criteria on groups $group_filter = []; if ($groups !== []) { global $groupMgr; $n = 0; foreach ($groups as $groupname) { $group = $groupMgr->getGroupByUniqueName($groupname); if ($group !== null) { if ($n > 0) { // place OR between each group criterion $group_filter[] = "OR"; } $nicknames = $group->getMembers(); $group_filter[] = ["nickname", "IN", $nicknames]; $n++; } else { // a group not found means a faulty query return []; } } } // execute filtering $game_results = null; if ($group_filter !== []) { $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $group_filter); } else { $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true); } $result = $game_results; } return $result; } function generate_report_by_groups(ReqHandler &$rh, array $params): string { global $gameMgr; global $user; $gameid = trim($params["gameid"]); $filter = trim($params["filter"] ?? ""); $groups = explode_list(trim($params["groups"])); $outtype = trim($params["outtype"] ?? "pdf"); // only PDF and TEX are valid if (!in_array($outtype, ["pdf", "tex"])) { return "FAIL"; } $game = $gameMgr->getGame($gameid); // verify game and access if (($game === null) || ((!$game->isUserContributorOrOwner($user->getNickname()) && !$user->hasQuizmasterPrivilege()))) { return "FAIL"; } // create destination directory and copy TeX frame file $repId = uniqid($outtype); $buildDir = REPORT_PDF_OUTPUT_DIR . DIRECTORY_SEPARATOR . $repId; mkdir($buildDir); $iter = new RecursiveDirectoryIterator(REPORT_TEMPLATE_DIR, FilesystemIterator::SKIP_DOTS); foreach ($iter as $dir_item) { if (!$iter->isDir()) { $src = $dir_item->getPathname(); $dst = $buildDir . DIRECTORY_SEPARATOR . $dir_item->getFilename(); copy($src, $dst); } } // assemble report $report = new Report($game->getName()); foreach ($groups as $groupname) { $stats = ReportBuilder::getStatsByFilters($gameid, $filter, $groupname, ""); $section = new ReportSection($groupname, $stats); $report->addSection($section); } // generate latex $report->saveTeX($buildDir); if ($outtype === "pdf") { // run LuaLaTeX twice chdir($buildDir); $tex_cmd = TEX_ENGINE . " -interaction=nonstopmode report.tex"; exec($tex_cmd); exec($tex_cmd); // rename output $origOutput = "report.pdf"; $output = $repId . ".pdf"; rename($origOutput, $output); $contentType = "application/pdf"; } else if ($outtype === "tex") { $output = $buildDir . DIRECTORY_SEPARATOR . $repId . ".zip"; $zip = new ZipArchive(); $zip->open($output, ZipArchive::CREATE | ZipArchive::OVERWRITE); $files = glob($buildDir . DIRECTORY_SEPARATOR . "*.tex"); foreach ($files as $file) { $zip->addFile($file, basename($file)); } $zip->addFromString("request.txt", "gameid: ${gameid}\ngroups: " . join(", ", $groups) . "\nfilter: ${filter}\nouttype: ${outtype}\n"); $zip->addFromString("request.url", $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"]); $zip->close(); $contentType = "application/zip"; } // set content type header("Content-Type: ${contentType}"); header("Content-Length: " . filesize($output)); header("Content-Disposition: attachment; filename=" . basename($output)); // deploy the generated PDF $f = fopen($output, "r"); if ($f !== false) { fpassthru($f); fclose($f); } return "OK"; } function generate_detailed_game_stats(ReqHandler &$rh, array $params): array { global $testMgr; $testids = json_decode(trim($params["testids"]), true); $gameid = trim($params["gameid"]); $stats = $testMgr->generateDetailedStats($gameid, $testids); return $stats; } function delete_tests(ReqHandler &$rh, array $params): string { global $testMgr; $ids = explode_list(trim($params["ids"])); foreach ($ids as $id) { $testMgr->deleteTest($id); } return "OK"; } $rh->add(["create_game", "update_game"], ["data"], PRIVILEGE_CREATOR, "create_update_game", RESP_JSON, "Create or update game."); $rh->add("get_all_game_headers", [], PRIVILEGE_CREATOR, "get_all_game_headers", RESP_JSON, "Get all game headers."); $rh->add("get_challenges", [], PRIVILEGE_CREATOR, "get_challenges", RESP_PLAIN, "Get game challenges."); $rh->add("delete_games", ["ids"], PRIVILEGE_CREATOR, "delete_games", RESP_PLAIN, "Delete games."); $rh->add("export_game_file_csv", ["gameid"], PRIVILEGE_CREATOR, "export_game_file_csv", RESP_NONE, "Export game CSV file."); $rh->add("get_results_by_gameid", ["gameid"], PRIVILEGE_CREATOR, "get_results_by_gameid", RESP_JSON, "Get game results."); $rh->add("generate_detailed_stats", ["gameid", "testids"], PRIVILEGE_CREATOR, "generate_detailed_game_stats", RESP_JSON, "Generate detailed game stats."); $rh->add("generate_report_by_groups", ["gameid", "groups"], PRIVILEGE_CREATOR, "generate_report_by_groups", RESP_NONE, "Generate game reports for each specified group."); $rh->add("delete_tests", ["ids"], PRIVILEGE_CREATOR, "delete_tests", RESP_PLAIN, "Delete tests."); // execute processing if user is a creator if ($privilege === PRIVILEGE_CREATOR) { goto process_and_print; } /* ------------------------ QUIZMASTER ACTIONS --------------------- */ function create_update_group(ReqHandler &$rh, array $params): string { global $user; global $groupMgr; global $userMgr; $update = $params[ReqHandler::ACTION_KEY] === "update_group"; $groupname = trim($params["groupname"]); $description = trim($params["description"]); $editors = (!$update) ? [] : explode_list(trim($params["editors"])); $owner = (!$update) ? $user->getNickname() : (trim($params["owner"]) ?: $user->getNickname()); $result = "FAIL"; if ($groupname != "") { if (!$update) { $groupMgr->addGroup($groupname, $owner, $description); $result = "OK"; } else { $gid = $params["id"]; $group = $groupMgr->getGroup($gid); if ($group !== null) { $group->disableAutoStoring(); $group->setName($groupname); $group->setDescription($description); // only an existing user might be an owner of a group if ($userMgr->getUser($owner) !== null) { $group->setOwner($owner); } $editors = array_intersect($editors, $group->getMembers()); // a user cannot be an editor if not participant of the group $group->setEditors($editors); $group->storeMods(); $group->enableAutoStoring(); $result = "OK"; } } } return $result; } function delete_groups(ReqHandler &$rh, array $params): string { global $groupMgr; $groups = explode_list($params["ids"] ?? ""); foreach ($groups as $g) { $groupMgr->deleteGroup($g); } return "OK"; } function get_all_groups(ReqHandler &$rh, array $params): array { global $groupMgr; $groups = $groupMgr->getAllGroups(); $a = []; foreach ($groups as $g) { $a[] = $g->toArray(); } return $a; } function search_groups(ReqHandler &$rh, array $params): array { global $groupMgr; $groups = $groupMgr->searchGroups($params["needle"]); $a = []; foreach ($groups as $g) { $a[] = $g->toArray(); } return $a; } function create_update_user(ReqHandler &$rh, array $params): string { global $userMgr; $update = $params[ReqHandler::ACTION_KEY] === "update_user"; $target_nickname = trim($params["nickname"]); $password = trim($params["password"]); $realname = trim($params["realname"]); $privilege = trim($params["privilege"]); $success = false; if (($target_nickname !== "")) { if ((!$update) && ($password !== "")) { // CREATE $success = $userMgr->addUser($target_nickname, $password, $realname, $privilege); } else if ($update) { // UPDATE $tuser = $userMgr->getUser($target_nickname); // load user data if ($tuser !== null) { // further field update $tuser->disableAutoStoring(); $tuser->setRealname($realname); $tuser->setPrivilege($privilege); // password replacement, if requested if ($password !== "") { $tuser->changePassword(password_hash($password, PASSWORD_DEFAULT), "", false); } $tuser->storeMods(); $tuser->enableAutoStoring(); $success = true; } } } return $success ? "OK" : "FAIL"; } function delete_users(ReqHandler &$rh, array $params): string { global $userMgr; $nicknames = explode_list($params["users"]); foreach ($nicknames as $nick) { $userMgr->deleteUser($nick); } return "OK"; } function get_all_users(ReqHandler &$rh, array $params): array { global $userMgr; $user_data_filtered = []; $all_users = $userMgr->getAllUsers(); for ($i = 0; $i < count($all_users); $i++) { $a = $all_users[$i]->toArray(); // convert user to array unset($a["password"]); // remove password from records $user_data_filtered[] = $a; } return $user_data_filtered; } function get_user_groups(ReqHandler &$rh, array $params): array { global $groupMgr; $groups = $groupMgr->getUserGroupIDs($params["nickname"]); $groupMgr->resolveGroupIds($groups); return $groups; } function get_game_groups(ReqHandler &$rh, array $params): array { global $groupMgr; $groups = $groupMgr->getGameGroupIDs($params["gameid"]); $groupMgr->resolveGroupIds($groups); return $groups; } function change_group_members(ReqHandler &$rh, array $params): string { global $groupMgr; global $user; global $userMgr; $group = $groupMgr->getGroup($params["groupid"]); if ($group !== null) { if ($group->isUserContributor($user->getNickname())) { $add = explode_list(trim($params["add"])); $add = $userMgr->sanitizeNicknames($add); $remove = explode_list(trim($params["remove"])); $remove = $userMgr->sanitizeNicknames($remove); $group->changeMembers($add, $remove); return "OK"; } } return "FAIL"; } function import_users_from_csv(ReqHandler &$rh, array $params): string { if (!isset($_FILES["users_table"])) { } return "OK"; } $rh->add("create_group", ["groupname", "description"], PRIVILEGE_QUIZMASTER, "create_update_group", RESP_PLAIN, "Create group."); $rh->add("update_group", ["groupname", "description", "owner", "editors", "id"], PRIVILEGE_QUIZMASTER, "create_update_group", RESP_PLAIN, "Update group."); $rh->add("delete_groups", ["ids"], PRIVILEGE_QUIZMASTER, "delete_groups", RESP_PLAIN, "Delete group."); $rh->add("get_all_groups", [], PRIVILEGE_QUIZMASTER, "get_all_groups", RESP_JSON, "Get all player groups."); $rh->add("search_groups", ["needle"], PRIVILEGE_QUIZMASTER, "search_groups", RESP_JSON, "Serach and fetch player groups."); $rh->add("change_group_members", ["groupid", "add", "remove"], PRIVILEGE_QUIZMASTER, "change_group_members", RESP_PLAIN, "Change group members."); $rh->add(["create_user", "update_user"], ["nickname", "password", "realname", "privilege"], PRIVILEGE_QUIZMASTER, "create_update_user", RESP_PLAIN, "Create or update user."); $rh->add("delete_users", ["users"], PRIVILEGE_QUIZMASTER, "delete_users", RESP_PLAIN, "Delete users."); $rh->add("get_all_users", [], PRIVILEGE_QUIZMASTER, "get_all_users", RESP_JSON, "Get all users."); $rh->add("get_user_groups", ["nickname"], PRIVILEGE_QUIZMASTER, "get_user_groups", RESP_JSON, "Get user's groups."); $rh->add("get_game_groups", ["gameid"], PRIVILEGE_QUIZMASTER, "get_game_groups", RESP_JSON, "Get game's groups."); $rh->add("import_users_from_csv", [], PRIVILEGE_QUIZMASTER, "import_users_from_csv", RESP_JSON, "Get all users."); //function test(ReqHandler &$rh, array $params): string //{ // $usrmgr = new UserMgr(); // $nicknames = $usrmgr->getAllNicknames(); // return join(", ", $nicknames); //} //$rh->add("test", [], PRIVILEGE_QUIZMASTER, "test", RESP_PLAIN, "Test."); // ---------- process_and_print: [$result, $success] = $rh->process($privilege); if ($success && ($result !== "")) { echo $result; }