diff --git a/class/ReqHandler.php b/class/ReqHandler.php new file mode 100644 index 0000000..65136ed --- /dev/null +++ b/class/ReqHandler.php @@ -0,0 +1,139 @@ +action = $action; + $this->params = $params; + $this->level = $level; + $this->handler = $handler; + $this->resp_type = $resp_type; + $this->hint = $hint; + $this->rh = &$rh; // assign be reference + } +} + +// Request Handler class +class ReqHandler +{ + private array $mapping; // action-handler mapping + private array $request; // reference to $_REQUEST + private array $files; // reference to $_FILES + private string $level; // max privilege level + + // Constructor + function __construct() + { + // create reference bindings + $this->request = &$_REQUEST; + $this->files = &$_FILES; + + $this->mapping = [PRIVILEGE_NONE => [], PRIVILEGE_PLAYER => [], PRIVILEGE_CREATOR => [], PRIVILEGE_QUIZMASTER => []]; // create assignment categories + $this->level = PRIVILEGE_NONE; + } + + // Add new handler + function add(string|array $actions, array $params, string $level, string $handler, string $resp_type, string $hint): bool + { + // convert single actions to array + $actions = is_string($actions) ? [$actions] : $actions; + + // cannot assign more than one handler to the same action + foreach ($actions as $action) { + if (in_array($action, $this->mapping[$level])) { + return false; + } + + // create the assignment object + $assignment = new ReqHandlerAssignment($action, $params, $level, $handler, $resp_type, $hint, $this); + $this->mapping[$level][$action] = $assignment; + } + + return true; + } + + const ACTION_KEY = "action"; + + // Process possible pending request. Also check access privilege. + function process(string $level = null, string $action_key = self::ACTION_KEY) : array { + // get action + $action = $this->request[$action_key] ?? ""; + + // don't process empty actions + if (trim($action) === "") { + return ["", false]; + } + + // if privilege level is specified, then store + $this->level = $level ?? $this->level; + + $resp = ""; + $success = false; + + // look up action + $categories = array_keys($this->mapping); + $highest_category_index = array_search($this->level, $categories); + for ($i = 0; ($i < count($categories)) && ($i <= $highest_category_index); $i++) { + // get category + $category = &$this->mapping[$categories[$i]]; + + // find the command level category that includes this action + if (array_key_exists($action, $category)) { + // get assignment + $assignment = &$category[$action]; + + // check that all required parameters were passed + $all_params_passed = true; + foreach ($assignment->params as $param) { + $all_params_passed &= array_key_exists($param, $this->request); + } + + // if params were provided, then invoke the callback + if ($all_params_passed) { + $handler = $assignment->handler; + $raw_resp = $handler($this, $this->request); + + // convert response to preset response type + if ($assignment->resp_type !== RESP_NONE) { // if command returns with anything at all + if ($assignment->resp_type === RESP_JSON) { // JSON + $resp = json_encode($raw_resp); + } else if ($assignment->resp_type === RESP_PLAIN) { // PLAIN + $resp = $raw_resp; + } + } + + // processing successful + $success = true; + + break; // break the loop + } + } + } + + return [$resp, $success]; + } + + // Set access privilege level + function set_privilege_level(string $level) { + $this->level = $level; + } +} \ No newline at end of file diff --git a/interface.php b/interface.php index ff9435e..b75fb4d 100644 --- a/interface.php +++ b/interface.php @@ -22,6 +22,9 @@ require_once "testmgr.php"; require_once "controller.php"; +require_once "class/ReqHandler.php"; + +// ------------------------ function patch_through_image(string $gameid, string $img_url) { $game_dir = get_game_dir_by_gameid($gameid); @@ -35,30 +38,40 @@ function patch_through_image(string $gameid, string $img_url) fclose($img_fp); } +// ------------------------ + $action = $_REQUEST["action"]; $result = ""; +$success = false; +$privilege = PRIVILEGE_NONE; -// no-login accessible actions -switch ($action) { - case "login": - { - $nickname = $_REQUEST["nickname"]; - $password = $_REQUEST["password"]; - if (check_user_credentials($nickname, $password)) { - session_start(); - $_SESSION["nickname"] = $nickname; - $result = "OK"; - } else { - $result = "FAIL"; - } - } - break; +// create request handler +$rh = new ReqHandler(); + +/* ------------ ACTIONS AVAILABLE WITHOUT LOGGING IN ---------- */ + +// login the user +function login(ReqHandler &$rh, array $params): string +{ + $nickname = $params["nickname"]; + $password = $params["password"]; + if (check_user_credentials($nickname, $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"])) || (count(get_user($_SESSION["nickname"])) == 0)) { - goto print_result; + goto process_and_print; } $user_data = get_user($_SESSION["nickname"]); @@ -66,443 +79,528 @@ $nickname = $user_data["nickname"]; $privilege = $user_data["privilege"]; $is_quizmaster = $privilege === PRIVILEGE_QUIZMASTER; -// login-requiring actions -switch ($action) { - case "logout": - { - $_SESSION = []; // clean up session data - setcookie(SESSION_NAME, "", -1); // invalidate cookie - } - break; - case "get_user_info": - { - $user_data_filtered = $user_data; - unset($user_data_filtered["password"]); - $result = json_encode($user_data_filtered); - } - break; - case "get_available_games": - { - $games_by_groups = []; - $groupids = $user_data["groups"]; - foreach ($groupids as $groupid) { - $group_data = get_group($groupid); - $game_collection = [ - "groupname" => $group_data["groupname"], - "description" => $group_data["description"], - "games" => [] - ]; - $gameids = $group_data["games"]; - foreach ($gameids as $gameid) { - $game = get_game($gameid); - $game_collection["games"][] = $game; - } - $games_by_groups[] = $game_collection; - } - $result = json_encode($games_by_groups); - } - break; - case "start_or_continue_test": - { - $gameid = trim($_REQUEST["gameid"] ?? ""); - $testid = create_or_continue_test($gameid, $nickname); - $result = $testid; - } - break; - case "get_results_overview": - { - $gameid = trim($_REQUEST["gameid"] ?? ""); - $concluded_tests = get_concluded_tests($gameid, $nickname); - $overviews = []; - foreach ($concluded_tests as $ct) { - $overview = [ - "testid" => $ct["_id"], - "start_time" => $ct["start_time"], - "end_time" => $ct["end_time"], - ...$ct["summary"] - ]; - $overviews[] = $overview; - } - $result = json_encode($overviews); - } - break; +/* ---------- 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_data; + $user_data_filtered = $user_data; + unset($user_data_filtered["password"]); + return $user_data_filtered; +} + +function get_available_games(ReqHandler &$rh, array $params): array +{ + global $user_data; + $games_by_groups = []; + $groupids = $user_data["groups"]; + foreach ($groupids as $groupid) { + $group_data = get_group($groupid); + $game_collection = [ + "groupname" => $group_data["groupname"], + "description" => $group_data["description"], + "games" => [] + ]; + $gameids = $group_data["games"]; + foreach ($gameids as $gameid) { + $game = get_game($gameid); + $game_collection["games"][] = $game; + } + $games_by_groups[] = $game_collection; + } + + return $games_by_groups; +} + +function start_or_continue_test(ReqHandler &$rh, array $params): string +{ + global $nickname; + $testid = create_or_continue_test($params["gameid"], $nickname); + return $testid; +} + +function get_results_overview(ReqHandler &$rh, array $params): array +{ + global $nickname; + $concluded_tests = get_concluded_tests($params["gameid"], $nickname); + $overviews = []; + foreach ($concluded_tests as $ct) { + $overview = [ + "testid" => $ct["_id"], + "start_time" => $ct["start_time"], + "end_time" => $ct["end_time"], + ...$ct["summary"] + ]; + $overviews[] = $overview; + } + return $overviews; +} + +$rh->add("logout", [], PRIVILEGE_PLAYER, "logout", RESP_PLAIN, "Log out the user."); +$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 -const TEST_RELATED_ACTIONS = ["get_test", "save_answer", "submit_test"]; -if (in_array($action, TEST_RELATED_ACTIONS)) { - - $testid = trim($_REQUEST["testid"] ?? ""); - $test_data = ($testid !== "") ? get_test($testid) : []; - $is_a_default_test_with_access = ($testid !== "") && ((count($test_data)) > 0) && (($test_data["nickname"] === $nickname)); - - $user_is_contributor_or_quizmaster = $is_quizmaster || is_user_contributor_to_game($test_data["gameid"], $nickname); - - if ($is_a_default_test_with_access || $user_is_contributor_or_quizmaster) { - - // update the test if timed - update_timed_tests([$test_data]); - - switch ($action) { - case "get_test": - { - $test_data_with_current_time = $test_data; - $test_data_with_current_time["current_time"] = time(); - $result = json_encode($test_data_with_current_time); - } - break; - case "save_answer": - { - $chidx = $_REQUEST["challenge_index"]; - $answeridx = $_REQUEST["answer_index"]; - save_answer($testid, $chidx, $answeridx); - } - break; - case "submit_test": - { - conclude_test($testid); - } - break; - } - } - - // $user_has_access_to_game = does_user_access_game($nickname, $gameid); - - // get_image needs special treatment - if ($action === "get_image") { - if ($is_a_default_test_with_access || $user_is_contributor_or_quizmaster) { // default case - $img_url = trim($_REQUEST["img_url"] ?? ""); - if ($img_url !== "") { - $gameid = $_REQUEST["gameid"]; - patch_through_image($gameid, $img_url); - } - } - } - +function does_test_belong_to_user(array $test_data): bool +{ + global $nickname; + return $test_data["nickname"] === $nickname; } -// creator or quizmaster actions -if (($privilege !== PRIVILEGE_CREATOR) && ($privilege !== PRIVILEGE_QUIZMASTER)) { - goto print_result; +function is_test_access_approved(array $test_data): bool +{ + global $nickname; + global $is_quizmaster; + + return does_test_belong_to_user($test_data) || is_user_contributor_to_game($test_data["gameid"], $nickname) || $is_quizmaster; } +function access_test_data(string $testid): array|null +{ + $testid = trim($testid); + $test_data = ($testid !== "") ? get_test($testid) : null; + + // fetch test data + if ($test_data === null) { + return []; + } + + // check if access is approved to the specific test + if (!is_test_access_approved($test_data)) { + return []; + } + + // update the test if timed + update_timed_tests([$test_data]); + + return $test_data; +} + + +function get_player_test(ReqHandler &$rh, array $params): array +{ + $result = []; + $test_data = access_test_data($params["testid"]); + + if ($test_data !== null) { + $test_data_with_current_time = $test_data; + $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 +{ + if (access_test_data($params["testid"] !== null)) { + save_answer($params["testid"], $params["challenge_index"], $params["answer_index"]); + return "OK"; + } else { + return "FAIL"; + } +} + +function submit_test(ReqHandler &$rh, array $params): string +{ + if (access_test_data($params["testid"] !== null)) { + conclude_test($params["testid"]); + return "OK"; + } else { + return "FAIL"; + } +} + +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 = $is_quizmaster ? "*" : $nickname; // "*" means every game -switch ($action) { - case "create_game": - case "update_game": - { - $update = $action === "update_game"; - $data = json_decode($_REQUEST["data"], true) ?? []; - if (($data === []) || (trim($data["name"] ?? "") === "")) { // no further processing - goto print_result; // ~exit... - } - $gameid = $data["_id"]; - $name = $data["name"]; - $description = $data["description"]; - $contributors = explode_list($data["contributors"] ?? ""); - $owner = $update ? trim($data["owner"] ?? $nickname) : $nickname; - $groups = explode_list($data["groups"] ?? ""); - $properties = $data["properties"] ?? []; +function create_update_game(ReqHandler &$rh, array $params): array +{ + global $nickname; + global $is_quizmaster; - $groupids = get_groupids_by_compounds($groups); // convert group compounds to _ids + $update = $params[ReqHandler::ACTION_KEY] === "update_game"; + $data = json_decode($params["data"], true) ?? []; + if (($data === []) || (trim($data["name"] ?? "") === "")) { // no further processing + return []; // ~exit... + } - // remove group ID's this user cannot edit - $groupids_with_editor_access = []; - foreach ($groupids as $groupid) { - if (is_user_editor_to_group($groupid, $nickname) || $is_quizmaster) { - $groupids_with_editor_access[] = $groupid; - } - } + // fetch fields + $gameid = $data["_id"]; + $name = $data["name"]; + $description = $data["description"]; + $contributors = explode_list($data["contributors"] ?? ""); + $owner = $update ? trim($data["owner"] ?? $nickname) : $nickname; + $groups = explode_list($data["groups"] ?? ""); + $properties = $data["properties"] ?? []; - // create or update - if (!$update) { - create_game($name, $owner, $description); - } else if (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster) { - $game_data = get_game($gameid); - if (count($game_data) !== 0) { - // group management - $old_groupids = $game_data["groups"]; // retain old groupids - $new_groupids = $groupids; // get new groupids - $groupids_add = array_diff($new_groupids, $old_groupids); // groups this user needs to be added to - $groupids_remove = array_diff($old_groupids, $new_groupids); // groups this user need to be removed from - foreach ($groupids_add as $groupid) { // execute insertion and removal - change_group_game_assignments($groupid, $gameid, null); - } - foreach ($groupids_remove as $groupid) { - change_group_game_assignments($groupid, null, $gameid); - } + // convert group compounds to group IDs + $groupids = get_groupids_by_compounds($groups); // convert group compounds to _ids - // re-fetch game data - $game_data = get_game($gameid); - - // update game header data - $game_data["name"] = $name; - $game_data["description"] = $description; - if (($game_data["owner"] === $nickname) || ($privilege === PRIVILEGE_QUIZMASTER)) { - $game_data["owner"] = $owner; - } - $game_data["contributors"] = array_intersect($contributors, get_all_nicknames()); - $game_data["properties"]["time_limit"] = $properties["time_limit"]; - $game_data["properties"]["repeatable"] = $properties["repeatable"]; - - // process game public flag: a game might be only public if not being time-constrained and is allowed to be taken multiple times - if (($properties["time_limit"] !== 0) || (!$properties["repeatable"])) { - $game_data["public"] = false; - } else { - $game_data["public"] = $data["public"]; - } - - // update game data - update_game($game_data); - - // 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 = get_game_dir_by_gameid($gameid); // 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)) { - $challenge_import_status = import_challenges_from_csv($csv_files[0], $gameid); - } - } - } else if ($file_type === "csv") { // a plain table was uploaded - $challenge_import_status = import_challenges_from_csv($file["tmp_name"], $gameid); - } - $result = json_encode($challenge_import_status); - } - } - } - } - break; - case "get_all_game_headers": - { - $game_headers = get_all_game_data_by_contributor_nickname($requester_nickname); - foreach ($game_headers as &$game_header) { - resolve_groupids($game_header["groups"]); - } - $result = json_encode($game_headers); - } - break; - case "get_challenges": - { - $gameid = ($_REQUEST["gameid"] ?? ""); - $game_data = get_game($gameid); - if ((count($game_data) > 0) && ($is_quizmaster || (is_user_contributor_to_game($gameid, $requester_nickname)))) { - $result = file_get_contents(get_game_file_by_gameid($gameid)); - } - } - break; - case "delete_games": - { - $gameids = explode_list(trim($_REQUEST["ids"] ?? "")); - foreach ($gameids as $gameid) { - if (($gameid !== "") && (is_user_owner_of_the_game($gameid, $nickname))) { // only the owner may delete a game - delete_game($gameid); - } - } - } - break; - case "export_game_file_csv": - { - $gameid = trim($_REQUEST["gameid"] ?? ""); - if (($gameid !== "") && is_user_contributor_to_game($gameid, $nickname)) { - $f = tmpfile(); - header("Content-Type: text/csv"); - header("Content-Disposition: attachment; filename=\"challenges_$gameid.csv\"\r\n"); - export_challenges_to_csv($f, $gameid); - fseek($f, 0); - fpassthru($f); - } - } - break; - case "get_results_by_gameid": - { - $gameid = trim($_REQUEST["gameid"] ?? ""); - $filter = trim($_REQUEST["filter"] ?? ""); - $ordering = trim($_REQUEST["orderby"] ?? ""); - if (($gameid !== "") && (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster)) { - $game_results = get_results_by_gameid($gameid, $filter, $ordering, true); - $result = json_encode($game_results); - } - } - break; - case "generate_detailed_stats": - { - $testids = json_decode(trim($_REQUEST["testids"] ?? "[]"), true); - $gameid = trim($_REQUEST["gameid"] ?? ""); - $stats = generate_detailed_stats($gameid, $testids); - $result = json_encode($stats); - } - break; - -// case "get_image": -// { -// $gameid = trim($_REQUEST["gameid"] ?? ""); -// $img_url = trim($_REQUEST["img_url"] ?? ""); -// patch_through_image($gameid, $img_url); + // remove group ID's this user cannot edit +// $groupids_with_editor_access = []; +// foreach ($groupids as $groupid) { +// if (is_user_editor_to_group($groupid, $nickname) || $is_quizmaster) { +// $groupids_with_editor_access[] = $groupid; // } -// break; -} +// } -// quizmaster actions -if ($privilege !== PRIVILEGE_QUIZMASTER) { - goto print_result; -} + // result + $result = []; -switch ($action) { - case "create_group": - case "update_group": - { - $update = $action === "update_group"; - $groupname = trim($_REQUEST["groupname"] ?? ""); - $description = trim($_REQUEST["description"] ?? ""); - $editors = explode_list(trim($_REQUEST["editors"] ?? "")); - $owner = (!$update) ? $user_data["nickname"] : trim($_REQUEST["owner"]); - if ($owner === "") { - $owner = $user_data["nickname"]; + // create or update + if (!$update) { + create_game($name, $owner, $description); + } else if (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster) { + $game_data = get_game($gameid); + if (count($game_data) !== 0) { + + // group management + $old_groupids = $game_data["groups"]; // retain old groupids + $new_groupids = $groupids; // get new groupids + $groupids_add = array_diff($new_groupids, $old_groupids); // groups this user needs to be added to + $groupids_remove = array_diff($old_groupids, $new_groupids); // groups this user need to be removed from + foreach ($groupids_add as $groupid) { // execute insertion and removal + change_group_game_assignments($groupid, $gameid, null); } - if ($groupname != "") { - switch ($action) { - case "create_group": - create_group($groupname, $owner, $description); - break; - case "update_group": - { - $gid = $_REQUEST["id"]; - $group = get_group($gid); - if (count($group) !== 0) { - $group["unique"] = manage_unique_in_siblings($gid, $groupname); // manage unique flag in case of renaming - $group["groupname"] = $groupname; - $group["description"] = $description; - $group["editors"] = array_intersect($editors, $group["users"]); // a user cannot be an editor if not part of the group - $group["owner"] = $owner; - update_group($group); - } + foreach ($groupids_remove as $groupid) { + change_group_game_assignments($groupid, null, $gameid); + } + + // re-fetch game data + $game_data = get_game($gameid); + + // update game header data + $game_data["name"] = $name; + $game_data["description"] = $description; + if (($game_data["owner"] === $nickname) || $is_quizmaster) { + $game_data["owner"] = $owner; + } + $game_data["contributors"] = array_intersect($contributors, get_all_nicknames()); + $game_data["properties"]["time_limit"] = $properties["time_limit"]; + $game_data["properties"]["repeatable"] = $properties["repeatable"]; + + // process game public flag: a game might be only public if not being time-constrained and is allowed to be taken multiple times + $game_data["public"] = ($properties["time_limit"] !== 0) || (!$properties["repeatable"]) ? false : $data["public"]; + + // update game data + update_game($game_data); + + // 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 = get_game_dir_by_gameid($gameid); // 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); } - break; + + // 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)) { + $challenge_import_status = import_challenges_from_csv($csv_files[0], $gameid); + } + } + } else if ($file_type === "csv") { // a plain table was uploaded + $challenge_import_status = import_challenges_from_csv($file["tmp_name"], $gameid); } - + $result = $challenge_import_status; } } - break; - case "delete_groups": - { - $groups = explode_list($_REQUEST["ids"] ?? ""); - foreach ($groups as $g) { - delete_group($g); - } - } - break; - case "get_all_groups": - $result = json_encode(get_all_groups()); - break; - case "search_groups": - { - $needle = $_REQUEST["needle"] ?? ""; - $result = json_encode(search_groups($needle)); - } - break; - case "create_user": - case "update_user": - { - $update = $action === "update_user"; - $target_nickname = trim($_REQUEST["nickname"] ?? ""); - $password = trim($_REQUEST["password"] ?? ""); - $groups = explode_list($_REQUEST["groups"] ?? ""); - $realname = trim($_REQUEST["realname"] ?? ""); - $privilege = trim($_REQUEST["privilege"] ?? PRIVILEGE_PLAYER); + } - $groupids = get_groupids_by_compounds($groups); // convert group compounds to _ids - - if (($target_nickname !== "")) { - if ((!$update) && ($password !== "")) { // CREATE - add_user($target_nickname, $password, $realname, $groupids, $privilege); - } else if ($update) { // UPDATE - $user_data = get_user($target_nickname); // load user data - - // group management - $old_groupids = $user_data["groups"]; // retain old groupids - $new_groupids = $groupids; // get new groupids - $groupids_add = array_diff($new_groupids, $old_groupids); // groups this user needs to be added to - $groupids_remove = array_diff($old_groupids, $new_groupids); // groups this user need to be removed from - foreach ($groupids_add as $groupid) { // execute insertion and removal - change_group_user_assignments($groupid, $target_nickname, null); - } - foreach ($groupids_remove as $groupid) { - change_group_user_assignments($groupid, null, $target_nickname); - } - - // re-fetch user - $user_data = get_user($target_nickname); // load user data - - // further field update - $user_data["realname"] = $realname; - $user_data["privilege"] = $privilege; - - // password replacement, if requested - if ($password !== "") { - $user_data["password"] = password_hash($password, PASSWORD_DEFAULT); - } - - update_user($user_data); - } - } - - } - break; - case "delete_users": - { - $users = explode_list($_REQUEST["users"] ?? ""); - foreach ($users as $g) { - delete_user($g); - } - } - break; - case "get_all_users": - { - $user_data_filtered = get_all_users(); - for ($i = 0; $i < count($user_data_filtered); $i++) { - unset($user_data_filtered[$i]["password"]); // remove password from records - resolve_groupids($user_data_filtered[$i]["groups"]); // resolve group IDs - } - $result = json_encode($user_data_filtered); - } - break; - case "import_users_from_csv": - { - if (!isset($_FILES["users_table"])) { - goto print_result; - } - } - break; + return $result; } +function get_all_game_headers(ReqHandler &$rh, array $params): array +{ + global $requester_nickname; + $game_headers = get_all_game_data_by_contributor_nickname($requester_nickname); + foreach ($game_headers as &$game_header) { + resolve_groupids($game_header["groups"]); + } + return $game_headers; +} + +function get_challenges(ReqHandler &$rh, array $params): string +{ + global $is_quizmaster; + global $requester_nickname; + $gameid = $params["gameid"]; + $game_data = get_game($gameid); + $result = ""; + if ((count($game_data) > 0) && ($is_quizmaster || (is_user_contributor_to_game($gameid, $requester_nickname)))) { + $result = file_get_contents(get_game_file_by_gameid($gameid)); + } + + return $result; +} + +function delete_games(ReqHandler &$rh, array $params): string +{ + global $nickname; + + $gameids = explode_list(trim($params["ids"])); + foreach ($gameids as $gameid) { + if (($gameid !== "") && (is_user_owner_of_the_game($gameid, $nickname))) { // only the owner may delete a game + delete_game($gameid); + } + } + + return "OK"; +} + +function export_game_file_csv(ReqHandler &$rh, array $params): string +{ + global $nickname; + $gameid = trim($params["gameid"]); + if (($gameid !== "") && is_user_contributor_to_game($gameid, $nickname)) { + $f = tmpfile(); + header("Content-Type: text/csv"); + header("Content-Disposition: attachment; filename=\"challenges_$gameid.csv\"\r\n"); + export_challenges_to_csv($f, $gameid); + fseek($f, 0); + fpassthru($f); + } + return ""; +} + +function get_player_results_by_gameid(ReqHandler &$rh, array $params): array +{ + global $nickname; + global $is_quizmaster; + + $gameid = trim($params["gameid"]); + $filter = trim($params["filter"] ?? ""); + $ordering = trim($params["orderby"] ?? ""); + + $result = []; + if (($gameid !== "") && (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster)) { + $game_results = get_results_by_gameid($gameid, $filter, $ordering, true); + $result = $game_results; + } + + return $result; +} + +function generate_detailed_game_stats(ReqHandler &$rh, array $params): array +{ + $testids = json_decode(trim($params["testids"]), true); + $gameid = trim($params["gameid"]); + $stats = generate_detailed_stats($gameid, $testids); + return $stats; +} + +$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_player_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."); + +// 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_data; + + $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_data["nickname"] : trim($params["owner"]); + + $result = "FAIL"; + if ($owner === "") { + $owner = $user_data["nickname"]; + } + if ($groupname != "") { + if (!$update) { + create_group($groupname, $owner, $description); + $result = "OK"; + } else { + $gid = $params["id"]; + $group = get_group($gid); + if (count($group) !== 0) { + $group["unique"] = manage_unique_in_siblings($gid, $groupname); // manage unique flag in case of renaming + $group["groupname"] = $groupname; + $group["description"] = $description; + $group["editors"] = array_intersect($editors, $group["users"]); // a user cannot be an editor if not part of the group + $group["owner"] = $owner; + update_group($group); + $result = "OK"; + } + } + } + return $result; +} + +function delete_groups(ReqHandler &$rh, array $params): string +{ + $groups = explode_list($params["ids"] ?? ""); + foreach ($groups as $g) { + delete_group($g); + } + return "OK"; +} + +function get_all_player_groups(ReqHandler &$rh, array $params): array +{ + return get_all_groups(); +} + +function search_player_groups(ReqHandler &$rh, array $params): array +{ + return search_groups($params["needle"]); +} + +function create_update_user(ReqHandler &$rh, array $params): string +{ + $update = $params[ReqHandler::ACTION_KEY] === "update_user"; + $target_nickname = trim($params["nickname"]); + $password = trim($params["password"]); + $groups = explode_list($params["groups"]); + $realname = trim($params["realname"]); + $privilege = trim($params["privilege"]); + + $groupids = get_groupids_by_compounds($groups); // convert group compounds to _ids + + $success = false; + if (($target_nickname !== "")) { + if ((!$update) && ($password !== "")) { // CREATE + $success = add_user($target_nickname, $password, $realname, $groupids, $privilege); + } else if ($update) { // UPDATE + $user_data = get_user($target_nickname); // load user data + if (count ($user_data) > 0) { + + // group management + $old_groupids = $user_data["groups"]; // retain old groupids + $new_groupids = $groupids; // get new groupids + $groupids_add = array_diff($new_groupids, $old_groupids); // groups this user needs to be added to + $groupids_remove = array_diff($old_groupids, $new_groupids); // groups this user need to be removed from + foreach ($groupids_add as $groupid) { // execute insertion and removal + change_group_user_assignments($groupid, $target_nickname, null); + } + foreach ($groupids_remove as $groupid) { + change_group_user_assignments($groupid, null, $target_nickname); + } + + // re-fetch user + $user_data = get_user($target_nickname); // load user data + + // further field update + $user_data["realname"] = $realname; + $user_data["privilege"] = $privilege; + + // password replacement, if requested + if ($password !== "") { + $user_data["password"] = password_hash($password, PASSWORD_DEFAULT); + } + + $success = update_user($user_data); + } + } + } + + return $success ? "OK" : "FAIL"; +} + +function delete_users(ReqHandler &$rh, array $params): string { + $users = explode_list($params["users"]); + foreach ($users as $g) { + delete_user($g); + } + return "OK"; +} + +function get_all_game_users(ReqHandler &$rh, array $params): array { + $user_data_filtered = get_all_users(); + for ($i = 0; $i < count($user_data_filtered); $i++) { + unset($user_data_filtered[$i]["password"]); // remove password from records + resolve_groupids($user_data_filtered[$i]["groups"]); // resolve group IDs + } + return $user_data_filtered; +} + +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_group", ["ids"], PRIVILEGE_QUIZMASTER, "delete_group", RESP_PLAIN, "Delete group."); +$rh->add("get_all_groups", [], PRIVILEGE_QUIZMASTER, "get_all_player_groups", RESP_JSON, "Get all player groups."); +$rh->add("search_groups", ["needle"], PRIVILEGE_QUIZMASTER, "search_player_groups", RESP_JSON, "Serach and fetch player groups."); + +$rh->add(["create_user", "update_user"], ["nickname", "password", "groups", "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_game_users", [], PRIVILEGE_QUIZMASTER, "get_all_users", RESP_JSON, "Get all users."); +$rh->add("import_users_from_csv", [], PRIVILEGE_QUIZMASTER, "import_", RESP_JSON, "Get all users."); + // ---------- -print_result: +process_and_print: -if ($result !== "") { +[$result, $success] = $rh->process($privilege); + +if ($success && ($result !== "")) { echo $result; } \ No newline at end of file diff --git a/style/spreadquiz_mobile.css b/style/spreadquiz_mobile.css index 65b1361..3e48cea 100644 --- a/style/spreadquiz_mobile.css +++ b/style/spreadquiz_mobile.css @@ -38,6 +38,10 @@ width: calc(100vw - 3em); } + section.answer label { + max-width: 80%; + } + section#infobox { top: unset; left: 0; diff --git a/usermgr.php b/usermgr.php index 7d99bc5..d0cd936 100644 --- a/usermgr.php +++ b/usermgr.php @@ -7,6 +7,7 @@ require_once "controller.php"; $userdb = new \SleekDB\Store(USERDB, DATADIR, ["timeout" => false]); +const PRIVILEGE_NONE = "none"; const PRIVILEGE_PLAYER = "player"; const PRIVILEGE_CREATOR = "creator"; const PRIVILEGE_QUIZMASTER = "admin"; // TODO: refactor!