commit dc9c7e5ded703bdcff739faf2c3ef1e4ca28e22a Author: Wiesner András Date: Tue Mar 5 16:07:47 2024 +0100 initial diff --git a/autologin.php b/autologin.php new file mode 100644 index 0000000..ec79d43 --- /dev/null +++ b/autologin.php @@ -0,0 +1,22 @@ + + + + + + + SpreadQuiz + + + + + + + +
+ +
+ + + + diff --git a/examples/minta_quiz_táblázat.xlsx b/examples/minta_quiz_táblázat.xlsx new file mode 100644 index 0000000..db628f1 Binary files /dev/null and b/examples/minta_quiz_táblázat.xlsx differ diff --git a/game_manager_frame.php b/game_manager_frame.php new file mode 100644 index 0000000..c56b381 --- /dev/null +++ b/game_manager_frame.php @@ -0,0 +1,66 @@ + + + + + + SpreadQuiz + + + + + + + + + +
+ + + + + + + + + + + +
NévLeírásTulajdonos
+
+
+ + +
+
+
+
+ Név:
+ Leírás:
+ Tulajdonos:
+ Szerkesztők:
+ Kérdés-fájl: + + +
+ Csoportok:
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/gamemgr.php b/gamemgr.php new file mode 100644 index 0000000..ad03759 --- /dev/null +++ b/gamemgr.php @@ -0,0 +1,186 @@ + false]); + +const DEFAULT_GAME_PROPERTIES = [ + "forward_only" => false, // player may traverse back and forth between challenges + "time_limit" => -1, // no time limit; otherwise, this field indicates time limit in seconds + "repeatable" => false // this test can be taken multiple times +]; + +function create_game(string $name, string $owner, string $description = "", array $properties = DEFAULT_GAME_PROPERTIES, array $contributors = [], array $challenges = []): bool +{ + global $testdb; + $game_data = [ + "name" => $name, + "owner" => $owner, + "contributors" => $contributors, + "description" => $description, + "game_file_present" => false, + "properties" => $properties, + "groups" => [], + ]; + $game_data = $testdb->insert($game_data); + + // prepare game context + $id = $game_data["_id"]; + $current_game_media_dir = GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $id; + mkdir($current_game_media_dir); + save_challenges($id, []); + return true; +} + +function get_game(string $gameid): array +{ + global $testdb; + return $testdb->findById($gameid); +} + +function update_game(array $game_data) +{ + global $testdb; + $testdb->update($game_data); +} + +function delete_game(string $gameid) +{ + global $testdb; + $game_data = get_game($gameid); + if (count($game_data) != 0) { + foreach ($game_data["groups"] as $groupid) { + change_group_game_assignments($groupid, null, $gameid); + } + $testdb->deleteById($gameid); + } +} + +function change_game_group_assignments(string $gid, $groupname_add, $groupname_remove) +{ + $game_data = get_game($gid); + if (count($game_data) != 0) { + alter_array_contents($game_data["groups"], $groupname_add, $groupname_remove); + update_game($game_data); // update user + } +} + +function get_all_games() +{ + global $testdb; + return $testdb->findAll(); +} + +function get_all_game_data_by_contributor_nickname(string $nickname): array +{ + global $testdb; + $game_headers = []; + if ($nickname !== "*") { + $game_data_array = $testdb->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]); + } else { + $game_data_array = $testdb->findAll(); + } + foreach ($game_data_array as $game_data) { + $game_headers[] = $game_data; + } + return $game_headers; +} + +function import_challenges_from_csv(string $csv_path, string $gameid) +{ + $game_data = get_game($gameid); + if (count($game_data) === []) { + return; + } + + $challenges = []; + + // load filled CSV file + $f = fopen($csv_path, "r"); + if (!$f) { // failed to open file + return; + } + while ($csvline = fgetcsv($f)) { + if (count($csvline) >= 3) { + $ch = [ + "question" => $csvline[0], + "image_url" => $csvline[1], + "correct_answer" => $csvline[2], + "answers" => array_filter(array_slice($csvline, 2), function ($v) { + return trim($v) !== ""; + }) + ]; + $challenges[] = $ch; + } + } + fclose($f); + + // save challenges + save_challenges($gameid, $challenges); + + // update game with game file present + $game_data["game_file_present"] = true; + update_game($game_data); +} + +function is_user_contributor_to_game(string $gameid, string $nickname): bool +{ + $game_data = get_game($gameid); + if (count($game_data) === 0) { + return false; + } + + return in_array($nickname, $game_data["contributors"]) || ($game_data["owner"] === $nickname); +} + +function is_user_owner_of_the_game(string $gameid, string $nickname): bool +{ + $game_data = get_game($gameid); + if (count($game_data) === 0) { + return false; + } + return $game_data["owner"] === $nickname; +} + +function save_challenges(string $gameid, array $challenges) +{ + file_put_contents(get_game_file_by_gameid($gameid), json_encode($challenges)); // store challenges in JSON-format +} + +function load_challenges(string $gameid) +{ + return json_decode(file_get_contents(get_game_file_by_gameid($gameid)), true); +} + +function export_challenges_to_csv($f, string $gameid) +{ + $game_data = get_game($gameid); + if ((count($game_data) === []) || (!$game_data["game_file_present"])) { + return; + } + + // load challenges + $challenges = load_challenges($gameid); + + // populate CSV file + foreach ($challenges as $ch) { + $csvline = [ + $ch["question"], + $ch["image_url"], + ]; + $csvline = array_merge($csvline, $ch["answers"]); + fputcsv($f, $csvline); + } +} + +function get_contributors(string $gameid): array +{ + $game_data = get_game($gameid); + return $game_data["contributors"]; +} + +function get_game_file_by_gameid(string $gameid) +{ + return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $gameid . DIRECTORY_SEPARATOR . GAME_FILE; +} \ No newline at end of file diff --git a/globals.php b/globals.php new file mode 100644 index 0000000..6981379 --- /dev/null +++ b/globals.php @@ -0,0 +1,34 @@ + + + + + + SpreadQuiz + + + + + + + +
+ + + + + + + + + + + +
NévLeírásTulajdonos
+
+
+ + +
+
+
+
+ Név:
+ Leírás:
+ Tulajdonos:
+ Szerkesztők:
+ Tagok: +
+ +
+
+ + + + + + + diff --git a/groupmgr.php b/groupmgr.php new file mode 100644 index 0000000..729f239 --- /dev/null +++ b/groupmgr.php @@ -0,0 +1,192 @@ + false]); + +function create_group(string $groupname, string $owner, string $description = "", array $editors = []): bool +{ + global $groupdb; + + // test name uniqueness + $unique = clear_unique_in_siblings($groupname); + + // initialize group data + $group_data = [ + "groupname" => $groupname, + "unique" => $unique, + "owner" => $owner, + "description" => $description, + "editors" => $editors, + "users" => [], + "games" => [] + ]; + $groupdb->insert($group_data); // insert group + return true; +} + +function get_group(string $groupid): array +{ + global $groupdb; + return $groupdb->findById($groupid); +} + +function update_group(array $group_data) +{ + global $groupdb; + $groupdb->update($group_data); +} + +function delete_group(string $groupid) +{ + global $groupdb; + $group_data = get_group($groupid); + if (count($group_data) !== 0) { + foreach ($group_data["users"] as $nickname) { // remove all references to this group + change_user_group_assignments($nickname, null, $groupid); + } + } + $groupdb->deleteById($groupid); +} + +function get_all_groups() +{ + global $groupdb; + return $groupdb->findAll(); +} + +function change_group_user_assignments(string $groupid, $nickname_add, $nickname_remove) +{ + $group_data = get_group($groupid); + if (count($group_data) != 0) { + // --------- UPDATE group assignments (DEV ONLY!)------ + + $group_data["editors"] = $group_data["editors"] ?: []; + + // --------- + + alter_array_contents($group_data["users"], $nickname_add, $nickname_remove); // add to local storage + alter_array_contents($group_data["editors"], null, $nickname_remove); // removed users must be excluded from the editors' list as well + update_group($group_data); // update user + if ($nickname_add !== null) { + change_user_group_assignments($nickname_add, $groupid, null); // add to user's storage remote + } + if ($nickname_remove !== null) { + change_user_group_assignments($nickname_remove, null, $groupid); // remove from user's storage + } + } +} + +function change_group_game_assignments(string $groupid, $gameid_add, $gameid_remove) +{ + $group_data = get_group($groupid); + if (count($group_data) != 0) { + alter_array_contents($group_data["games"], $gameid_add, $gameid_remove); // add to local storage + update_group($group_data); // update user + if ($gameid_add !== null) { + change_game_group_assignments($gameid_add, $groupid, null); // add to user's storage remote + } + if ($gameid_remove !== null) { + change_game_group_assignments($gameid_remove, null, $groupid); // remove from user's storage + } + } +} + +function search_groups(string $needle): array +{ + if (strlen($needle) < 3) { + return []; + } + + global $groupdb; + $parts = explode("#", $needle, 2); + $groupname = $parts[0]; + $group_data_array = []; + if (count($parts) === 1) { + $group_data_array = $groupdb->findBy(["groupname", "LIKE", "%$groupname%"]); + } else if (count($parts) > 1) { + $groupid = $parts[1]; + $group_data_array = $groupdb->findBy([["groupname", "LIKE", "%$groupname%"], "AND", ["_id", "LIKE", "%$groupid%"]]); + } + + $results = []; + foreach ($group_data_array as $group_data) { + $results[] = [ + "groupname" => $group_data["groupname"], + "_id" => $group_data["_id"], + "unique" => $group_data["unique"] + ]; + } + return $results; +} + +function clear_unique_in_siblings($groupname): bool +{ + // make test on name uniqueness + global $groupdb; + $twins = $groupdb->findBy(["groupname", "=", "$groupname"]); + $unique = count($twins) == 0; + if (count($twins) === 1) { // if fails, then also indicate in the original group that its name is no longer unique + $twins[0]["unique"] = false; + update_group($twins[0]); + } + return $unique; +} + +function get_groupids_by_compounds(array $compounds): array +{ + global $groupdb; + $groupids = []; + foreach ($compounds as $compound) { + if (trim($compound) === "") { // skip empty entries + continue; + } + + // fetch the group + $parts = explode("#", $compound); + $group_data = []; + if (count($parts) === 1) { + $fetch_cmd = ["groupname", "=", $parts[0]]; + $group_data = $groupdb->findBy($fetch_cmd); + if (count($group_data) == 1) { // too many hits + $group_data = $group_data[0]; + } else { + $group_data = []; + } + } else { + $group_data = $groupdb->findById($parts[1]); + } + if ($group_data !== []) { + $groupids[] = $group_data["_id"]; + } + } + return $groupids; +} + +function is_user_editor_to_group(string $groupid, string $nickname): bool +{ + $group_data = get_group($groupid); + if (count($group_data) === 0) { + return false; + } + + return in_array($nickname, $group_data["editors"]) || ($group_data["owner"] === $nickname); +} + +function get_group_unique_name(string $groupid): string +{ + $group_data = get_group($groupid); + if (count($group_data) !== 0) { + return $group_data["groupname"] . (!$group_data["unique"] ? "#" . $groupid : ""); + } + return ""; +} + +function resolve_groupids(array &$groups): void +{ + for ($i = 0; $i < count($groups); $i++) { + $groups[$i] = get_group_unique_name($groups[$i]); + } +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..25f6806 --- /dev/null +++ b/index.php @@ -0,0 +1,10 @@ + + + + + SpreadQuiz + + + + + diff --git a/install.php b/install.php new file mode 100644 index 0000000..97191e7 --- /dev/null +++ b/install.php @@ -0,0 +1,24 @@ + $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; +} + +// creator or quizmaster actions +if (($privilege !== PRIVILEGE_CREATOR) && ($privilege !== PRIVILEGE_QUIZMASTER)) { + goto print_result; +} + +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"] ?: ""); + + $groupids = get_groupids_by_compounds($groups); // convert group compounds to _ids + + // remove group ID's this user cannot edit + $groupids_with_editor_access = []; + foreach ($groupids as $groupid) { + if (is_user_editor_to_group($groupid, $nickname)) { + $groupids_with_editor_access[] = $groupid; + } + } + $groupids_with_editor_access = $groupid; + + if (!$update) { + create_game($name, $owner, $description); + } else if (is_user_contributor_to_game($gameid, $nickname)) { + $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); + } + + // 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()); + update_game($game_data); + + // update game file if supplied + if (isset($_FILES["game_file"])) { + import_challenges_from_csv($_FILES["game_file"]["tmp_name"], $data["_id"]); + } + } + } + } + break; + case "get_all_game_headers": + { + $requester_nickname = ($privilege === PRIVILEGE_QUIZMASTER) ? "*" : $nickname; // "*" means every game + $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 "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; +} + +// quizmaster actions +if ($privilege !== PRIVILEGE_QUIZMASTER) { + goto print_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"]; + } + if ($groupname != "") { + switch ($action) { + case "create_group": + create_group($groupname, $owner, $description, $editors); + break; + case "update_group": + { + $gid = $_REQUEST["id"]; + $group = get_group($gid); + if (count($group) !== 0) { + $group["unique"] = clear_unique_in_siblings($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); + } + } + break; + } + + } + } + 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; +} + +// ---------- + +print_result: + +if ($result !== "") { + echo $result; +} \ No newline at end of file diff --git a/js/default_frame.js b/js/default_frame.js new file mode 100644 index 0000000..533204e --- /dev/null +++ b/js/default_frame.js @@ -0,0 +1,29 @@ +function list_available_games() { + let game_list_panel = document.getElementById("game_list_panel"); + + let req = { + action: "get_available_games" + } + request(req).then(resp => { + let games_by_groups = JSON.parse(resp); + games_by_groups.forEach((game_collection) => { + let group_box = document.createElement("section"); + group_box.classList.add("group-box"); + let group_box_caption = document.createElement("span"); + group_box_caption.classList.add("group-box-caption"); + group_box_caption.innerHTML = game_collection["groupname"]; + let group_box_inner = document.createElement("section"); + group_box_inner.classList.add("group-box-inner"); + group_box.append(group_box_caption, group_box_inner); + + game_collection["games"].forEach((game) => { + let game_box = document.createElement("section"); + game_box.classList.add("game-box"); + game_box.innerHTML = game["name"]; + group_box_inner.appendChild(game_box); + }); + + game_list_panel.appendChild(group_box); + }); + }); +} \ No newline at end of file diff --git a/js/gamemgr.js b/js/gamemgr.js new file mode 100644 index 0000000..8ac1e18 --- /dev/null +++ b/js/gamemgr.js @@ -0,0 +1,175 @@ +function list_all_games() { + let req = {action: "get_all_game_headers"}; + let tbody = document.getElementById("game_manager_table"); + tbody.innerHTML = ""; + request(req).then(resp => { + let games = JSON.parse(resp); + for (let i = 0; i < games.length; i++) { + let g = games[i]; + let row = document.createElement("tr"); + let chkbox = document.createElement("input"); + chkbox.type = "checkbox"; + chkbox.name = "game_chkbox"; + chkbox.game = g; + let tdChkBox = document.createElement("td"); + tdChkBox.appendChild(chkbox); + tdChkBox.classList.add("checkbox"); + let tdGameName = create_table_cell(g["name"]); + let tdGameDescription = create_table_cell(g["description"]); + let tdOwner = create_table_cell(g["owner"]); + row.append(tdChkBox, tdGameName, tdGameDescription, tdOwner); + tbody.appendChild(row); + + let edit_group_action = () => { + create_edit_game(g); + }; + + tdGameName.addEventListener("click", edit_group_action); + tdGameDescription.addEventListener("click", edit_group_action); + tdOwner.addEventListener("click", edit_group_action); + } + }); +} + +var EDITED_GAME = null; + +function create_edit_game(game = null) { + EDITED_GAME = game; + let updating = game !== null; + + let nameF = document.getElementById("game_name"); + let descriptionF = document.getElementById("game_description"); + let submit_btn = document.getElementById("game_editor_submit_btn"); + let ownerF = document.getElementById("game_owner"); + let contributorsF = document.getElementById("game_contributors"); + let gameFileF = document.getElementById("game_file"); + let download_challenges_btn = document.getElementById("download_challenges_btn"); + let show_game_file_upload_btn = document.getElementById("show_game_file_upload"); + let cancel_game_file_upload_btn = document.getElementById("cancel_game_file_upload"); + let groupF = document.getElementById("game_groups"); + + if (!updating) { // creating a new game + nameF.value = ""; + descriptionF.value = ""; + submit_btn.value = "Létrehozás" + ownerF.value = USERDATA["nickname"]; + ownerF.readOnly = true; + contributorsF.value = ""; + groupF.value = ""; + } else { // editing an existing one + nameF.value = game["name"]; + descriptionF.value = game["description"]; + submit_btn.value = "Mentés" + ownerF.value = game["owner"]; + ownerF.readOnly = false; + contributorsF.value = game["contributors"].join(", "); + groupF.value = game["groups"].join(", "); + } + gameFileF.value = ""; + + let game_file_present = updating && game["game_file_present"]; + if (game_file_present) { + show(download_challenges_btn); + } + show_hide_gamefile_upload(false); + + submit_btn.onclick = () => { + let game_name = document.getElementById("game_name").value.trim(); + if (game_name !== "") { + let reqData = { + name: game_name, + description: descriptionF.value.trim(), + owner: updating ? ownerF.value.trim() : USERDATA["nickname"], + contributors: contributorsF.value.trim(), + groups: groupF.value.trim() + }; + if (updating) { + reqData["_id"] = game["_id"]; + } + let req = { + action: updating ? "update_game" : "create_game", + data: JSON.stringify(reqData) + }; + if (gameFileF.files.length > 0) { // append game file if selected + req["game_file"] = gameFileF.files[0]; + } + request(req).then(resp => { + list_all_games(); + }); + hide("game_editor_window"); + } + }; + + show("game_editor_window"); +} + +function show_hide_gamefile_upload(en) { + if (en) { + // hide("download_challenges_btn"); + hide("show_game_file_upload"); + show("game_file"); + show("cancel_game_file_upload"); + } else { + // show("download_challenges_btn"); + show("show_game_file_upload"); + hide("game_file"); + hide("cancel_game_file_upload"); + document.getElementById("game_file").value = ""; + } +} + +function download_challenges() { + let action = "export_game_file_csv"; + let gameid = EDITED_GAME["_id"]; + window.open(`interface.php?action=${action}&gameid=${gameid}`, "_blank"); +} + +function get_selected_games() { + let selected_chkboxes = document.getElementsByName("game_chkbox"); + let selected_games = []; + selected_chkboxes.forEach((chkbox) => { + if (chkbox.checked) { + selected_games.push(chkbox.game); + } + }); + return selected_games; +} + +function delete_games() { + let games = get_selected_games(); + if (games.length === 0) { + return; + } + + let game_names = []; + let game_ids = []; + games.forEach((g) => { + game_names.push(g["name"]); + game_ids.push(g["_id"]); + }); + let msg = "Biztosan törölni kívánja a következő játéko(ka)t?\n\n" + game_names.join(", ") + "\n\n" + + "A törlés nem vonható vissza!"; + if (confirm(msg)) { + let req = {action: "delete_games", ids: game_ids.join(",")}; + request(req).then(resp => { + list_all_games(); + }); + } +} + +// function hint_all_groups(target_element_id) { +// const hintbox_insert_fn = (record) => { +// let targetF = document.getElementById(target_element_id); +// let groups = explode_sanitize_string_list(targetF.value); +// groups.pop(); +// groups.push(record); +// targetF.value = groups.join(", "); +// close_hintbox(true); +// } +// +// let req = { +// action: "search_groups", +// } +// +// open_hintbox_at(target_element_id, req, print_group_name, hintbox_insert_fn); +// } \ No newline at end of file diff --git a/js/groupmgr.js b/js/groupmgr.js new file mode 100644 index 0000000..892993e --- /dev/null +++ b/js/groupmgr.js @@ -0,0 +1,123 @@ +function list_all_groups() { + let req = {action: "get_all_groups"}; + let tbody = document.getElementById("group_manager_table"); + tbody.innerHTML = ""; + request(req).then(resp => { + let groups = JSON.parse(resp); + for (let i = 0; i < groups.length; i++) { + let g = groups[i]; + let row = document.createElement("tr"); + let chkbox = document.createElement("input"); + chkbox.type = "checkbox"; + chkbox.name = "group_chkbox"; + chkbox.group = g; + let tdChkBox = document.createElement("td"); + tdChkBox.appendChild(chkbox); + tdChkBox.classList.add("checkbox"); + let tdGroupName = create_table_cell(print_group_name(g)); + let tdGroupDescription = create_table_cell(g["description"]); + let tdOwner = create_table_cell(g["owner"]); + row.append(tdChkBox, tdGroupName, tdGroupDescription, tdOwner); + tbody.appendChild(row); + + let edit_group_action = () => { + create_edit_group(g); + }; + + tdGroupName.addEventListener("click", edit_group_action); + tdGroupDescription.addEventListener("click", edit_group_action); + tdOwner.addEventListener("click", edit_group_action); + } + }); +} + +function create_edit_group(group = null) { + let update = group !== null; + + let groupnameF = document.getElementById("groupname"); + let group_descriptionF = document.getElementById("group_description"); + let submit_btn = document.getElementById("group_editor_submit_btn"); + let group_ownerF = document.getElementById("group_owner"); + let group_editorsF = document.getElementById("group_editors"); + let group_membersF = document.getElementById("group_members"); + + if (!update) { // create a new group + groupnameF.value = ""; + group_descriptionF.value = ""; + submit_btn.value = "Létrehozás" + group_ownerF.value = USERDATA["nickname"]; + group_ownerF.readOnly = true; + group_editorsF.value = ""; + group_membersF.value = ""; + } else { // update and existing one + groupnameF.value = group["groupname"]; + group_descriptionF.value = group["description"]; + submit_btn.value = "Mentés" + group_ownerF.value = group["owner"]; + group_ownerF.readOnly = false; + group_editorsF.value = group["editors"].join(", "); + group_membersF.value = group["users"].join(", "); + } + + submit_btn.onclick = () => { + let groupname = document.getElementById("groupname").value.trim(); + if (groupname !== "") { + let req = { + action: update ? "update_group" : "create_group", + groupname: groupname, + description: group_descriptionF.value.trim(), + editors: group_editorsF.value.trim() + }; + if (update) { + req["id"] = group["_id"]; + } + request(req).then(resp => { + list_all_groups(); + }); + hide("group_editor_window"); + } + }; + + show("group_editor_window"); +} + +function get_selected_groups() { + let selected_chkboxes = document.getElementsByName("group_chkbox"); + let selected_groups = []; + selected_chkboxes.forEach((chkbox) => { + if (chkbox.checked) { + selected_groups.push(chkbox.group); + } + }); + return selected_groups; +} + +function delete_groups() { + let groups = get_selected_groups(); + if (groups.length === 0) { + return; + } + + let group_names = []; + let group_ids = []; + groups.forEach((g) => { + group_names.push(g["groupname"]); + group_ids.push(g["_id"]); + }); + let msg = "Biztosan törölni kívánja a következő csoporto(ka)t?\n\n" + group_names.join(", ") + "\n\n" + + "A törlés nem vonható vissza!"; + if (confirm(msg)) { + let req = {action: "delete_groups", ids: group_ids.join(",")}; + request(req).then(resp => { + list_all_groups(); + }); + } +} + +function print_group_name(group_data) { + let record = group_data["groupname"]; + if (!group_data["unique"]) { + record += "#" + group_data["_id"]; + } + return record; +} \ No newline at end of file diff --git a/js/hintbox.js b/js/hintbox.js new file mode 100644 index 0000000..7070c74 --- /dev/null +++ b/js/hintbox.js @@ -0,0 +1,70 @@ +function explode_sanitize_string_list(list, explodeAt = ",") { + let elements = list.split(explodeAt); + let sanitized = []; + elements.forEach((e) => { + if (e.trim() !== "") { + sanitized.push(e); + } + }); + return sanitized; +} + +const HINTBOX_ID = "HINTBOX"; + +function close_hintbox(close_if_focused = false) { + setTimeout(() => { + if (close_if_focused || (document.getElementById(HINTBOX_ID).firstChild !== document.activeElement)) { + hide(HINTBOX_ID); + } + }, 20); +} +function open_hintbox(x, y, contents, print_fun, insert_fun) { + let hbw = document.getElementById(HINTBOX_ID); + if (hbw === null) { + hbw = document.createElement("section"); + hbw.id = HINTBOX_ID; + hbw.classList.add("hintbox-window"); + let lb = document.createElement("select"); + lb.size = 5; + lb.style.width = "100%"; + hbw.appendChild(lb); + document.body.appendChild(hbw); + } + let lb = hbw.firstChild; + + hbw.style.left = `${x}px`; + hbw.style.top = `${y}px`; + + lb.innerHTML = ""; + contents.forEach((record) => { + let line = print_fun(record); + let opt = document.createElement("option"); + opt.value = line; + opt.innerText = line; + lb.appendChild(opt); + }); + + lb.ondblclick = () => { + insert_fun(lb.value); + }; + show(HINTBOX_ID); +} + +function open_hintbox_at(target_element_id, req_data, print_fn, insert_fn) { + let targetF = document.getElementById(target_element_id); + + let bbox = targetF.getBoundingClientRect(); + let list_str = targetF.value.trim().split(","); + if (list_str.length > 0) { + let last_le_str = list_str[list_str.length - 1].trim(); + if (last_le_str.length > 2) { + req_data["needle"] = last_le_str; // auto-insert needle + request(req_data).then(resp => { + let groups = JSON.parse(resp); + let x = bbox.x + bbox.width; + let y = bbox.y; + open_hintbox(x, y, groups, print_fn, insert_fn); + }); + } + } +} \ No newline at end of file diff --git a/js/o.js b/js/o.js new file mode 100644 index 0000000..66d8ebb --- /dev/null +++ b/js/o.js @@ -0,0 +1,21 @@ +function o(input) { + var o; + if (typeof input === "string") + o = document.getElementById(input); + else if (typeof input === "object") + o = input; + return o; +} + +// megjelenítés / eltüntetés +function show(obj) { + o(obj).setAttribute("shown", "true"); +} + +function hide(obj) { + o(obj).setAttribute("shown", "false"); +} + +function toggle_show(obj) { + o(obj).setAttribute("shown", o(obj).getAttribute("shown") === "false"); +} diff --git a/js/req.js b/js/req.js new file mode 100644 index 0000000..a4bad86 --- /dev/null +++ b/js/req.js @@ -0,0 +1,31 @@ +// kérés indítása a szerver felé (eredeti: KL.) +function request(data, url = "interface.php", method = "POST") { + return new Promise((resolve, reject) => { + var fd; + + // ha van adat megadva... + if (data != null) { + fd = new FormData(); + + // mezők hozzáfűzése a kéréshez + for (let prop in data) { + if (Object.prototype.hasOwnProperty.call(data, prop)) { + fd.append(prop, data[prop]); + } + } + + } + + // kérés feladása + fetch(url, { + method: method, + body: fd, + }) + .then(response => response.text()) + .then(data => resolve(data)) + .catch((error) => { + console.error('Error: ', error); + reject(error); + }); + }); +} diff --git a/js/spreadquiz.js b/js/spreadquiz.js new file mode 100644 index 0000000..7589a58 --- /dev/null +++ b/js/spreadquiz.js @@ -0,0 +1,52 @@ +function login() { + let nicknameF = document.getElementById("nickname"); // fetch fields + let pwF = document.getElementById("password"); + let nickname = nicknameF.value; // extract values + let pw = pwF.value; + + let loginReq = { + action: "login", + nickname: nickname, + password: pw + }; + + request(loginReq).then(resp => { + if (resp === "OK") { + location.href = "main.php" + } + }); +} + +function open_in_content_frame(url) { + document.getElementById("content_frame").src = url; +} + +var USERDATA = {}; +function load_userdata() { + let req = {action: "get_user_info"}; + request(req).then(resp => { + USERDATA = JSON.parse(resp); + }); +} + +load_userdata(); + +function create_table_cell(content, styleClass = "") { + if (content.trim() === "") { + content = "(üres)"; + } + let td = document.createElement("td"); + td.innerHTML = content; + if (styleClass !== "") { + td.classList.add(styleClass); + } + return td; +} + +// --------------- + +function highlight_row(nickname) { + let hl_on = document.getElementById("user_chk_" + nickname).checked; + let row = document.getElementById("row_" + nickname); + row.setAttribute("highlight", hl_on ? "true" : "false"); +} \ No newline at end of file diff --git a/js/testground.js b/js/testground.js new file mode 100644 index 0000000..a55de24 --- /dev/null +++ b/js/testground.js @@ -0,0 +1,30 @@ +function populate_test(test_id) { + let test_display = document.getElementById("test_display"); + + let req = { + action: "get_test", + id: test_id + } + request(req).then(resp => { + let test_data = JSON.parse(resp); + test_data["challenges"].forEach((challenge) => { + let challenge_box = document.createElement("section"); + challenge_box.classList.add("challenge"); + let question = document.createElement("span"); + question.classList.add("question"); + question.innerHTML = challenge["question"]; + let answer_container = document.createElement("section"); + answer_container.classList.add("answer-container"); + challenge_box.append(question, answer_container); + + challenge["answers"].forEach((answer) => { + let answer_section = document.createElement("section"); + answer_section.classList.add("answer"); + answer_section.innerHTML = answer; + answer_container.appendChild(answer_section); + }); + + test_display.appendChild(challenge_box); + }); + }); +} \ No newline at end of file diff --git a/js/usermgr.js b/js/usermgr.js new file mode 100644 index 0000000..bab68da --- /dev/null +++ b/js/usermgr.js @@ -0,0 +1,164 @@ +function list_all_users() { + let tbody = document.getElementById("user_manager_table_body"); + tbody.innerHTML = ""; + let req = {action: "get_all_users"}; + request(req).then(resp => { + let users = JSON.parse(resp); + for (let i = 0; i < users.length; i++) { + let u = users[i]; + let row = document.createElement("tr"); + let chkbox = document.createElement("input"); + chkbox.type = "checkbox"; + chkbox.name = "user_chkbox"; + chkbox.user = u; + let tdChkBox = document.createElement("td"); + tdChkBox.appendChild(chkbox); + tdChkBox.classList.add("checkbox"); + let tdNickName = create_table_cell(u["nickname"]); + let tdRealName = create_table_cell(u["realname"]); + let tdGroups = create_table_cell(u["groups"].join(", ")); + let tdPrivilege = create_table_cell(u["privilege"]); + row.append(tdChkBox, tdNickName, tdRealName, tdGroups, tdPrivilege); + tbody.appendChild(row); + + let edit_user_action = () => { + if (get_selected_users().length === 0) { + edit_user(u); + } + }; + + tdNickName.addEventListener("click", edit_user_action); + tdRealName.addEventListener("click", edit_user_action); + tdGroups.addEventListener("click", edit_user_action); + tdPrivilege.addEventListener("click", edit_user_action) + } + }); +} + +function create_new_user() { + const generateRandomString = () => { + return Math.floor(Math.random() * Date.now()).toString(36); + }; + + let nicknameF = document.getElementById("nickname"); + let realnameF = document.getElementById("realname"); + let passwordF = document.getElementById("password"); + let groupsF = document.getElementById("groups"); + let privilegeF = document.getElementById("privilege"); + let submit_btn = document.getElementById("user_editor_submit_btn"); + + nicknameF.value = ""; + nicknameF.readOnly = false; + realnameF.value = ""; + passwordF.type = "text"; + passwordF.value = generateRandomString(); + passwordF.readOnly = true; + groupsF.value = ""; + submit_btn.value = "Létrehozás" + + submit_btn.onclick = () => { + let nickname = nicknameF.value.trim(); + if (nickname !== "") { + let req = { + action: "create_user", + nickname: nickname, + realname: realnameF.value.trim(), + password: passwordF.value, + groups: groupsF.value.trim(), + privilege: privilegeF.value + }; + request(req).then(resp => { + list_all_users(); + }); + hide("user_editor_window"); + } + }; + + show("user_editor_window"); +} + +function edit_user(user) { + let nicknameF = document.getElementById("nickname"); + let realnameF = document.getElementById("realname"); + let passwordF = document.getElementById("password"); + let groupsF = document.getElementById("groups"); + let privilegeF = document.getElementById("privilege"); + let submit_btn = document.getElementById("user_editor_submit_btn"); + + nicknameF.value = user["nickname"]; + nicknameF.readOnly = true; + realnameF.value = user["realname"]; + passwordF.type = "password"; + passwordF.value = ""; + passwordF.readOnly = false; + groupsF.value = user["groups"].join(", "); + privilegeF.value = user["privilege"]; + submit_btn.value = "Mentés" + + submit_btn.onclick = () => { + let nickname = nicknameF.value.trim(); + if (nickname !== "") { + let req = { + action: "update_user", + nickname: nickname, + realname: realnameF.value.trim(), + password: passwordF.value, + groups: groupsF.value.trim(), + privilege: privilegeF.value + }; + request(req).then(resp => { + list_all_users(); + }); + hide("user_editor_window"); + } + }; + + show("user_editor_window"); +} + +function get_selected_users() { + let selected_chkboxes = document.getElementsByName("user_chkbox"); + let selected_users = []; + selected_chkboxes.forEach((chkbox) => { + if (chkbox.checked) { + selected_users.push(chkbox.user); + } + }); + return selected_users; +} + +function delete_users() { + let users = get_selected_users(); + if (users.length === 0) { + return; + } + let user_nicknames = []; + users.forEach((u) => { + user_nicknames.push(u["nickname"]); + }); + let msg = "Biztosan törölni kívánja a következő felhasználó(ka)t?\n\n" + user_nicknames.join(", ") + "\n\n" + + "A törlés nem vonható vissza!"; + if (confirm(msg)) { + let req = {action: "delete_users", users: user_nicknames.join(",")}; + request(req).then(resp => { + list_all_users(); + }); + } +} + +function hint_all_groups(target_element_id) { + const hintbox_insert_fn = (record) => { + let targetF = document.getElementById(target_element_id); + let groups = explode_sanitize_string_list(targetF.value); + groups.pop(); + groups.push(record); + targetF.value = groups.join(", "); + close_hintbox(true); + } + + let req = { + action: "search_groups", + } + + open_hintbox_at(target_element_id, req, print_group_name, hintbox_insert_fn); +} \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..c6a6f1d --- /dev/null +++ b/login.php @@ -0,0 +1,24 @@ + + + + + + SpreadQuiz :: Bejelentkezés + + + + + + + + + diff --git a/main.php b/main.php new file mode 100644 index 0000000..339d4a4 --- /dev/null +++ b/main.php @@ -0,0 +1,50 @@ + + + + + + + + SpreadQuiz + + + + + +
+
+ +
+
+
+ +
+ + + + + + + +
+ +
+
+ + + diff --git a/style/spreadquiz.css b/style/spreadquiz.css new file mode 100644 index 0000000..b707720 --- /dev/null +++ b/style/spreadquiz.css @@ -0,0 +1,197 @@ +*[shown="false"] { + visibility: hidden; + display: none; + opacity: 0; + width: 0; + height: 0; +} + +*[shown="true"] { + opacity: 1; +} + +/* ----------------- */ + +section#screen_panel { + display: block; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +section#content_pane, section#info_pane { + position: absolute; + display: block; + top: 0; + bottom: 0; +} + +section#content_pane { + left: 0; + width: 80vw; + background-color: lightcyan; +} + +section#info_pane { + right: 0; + width: 20vw; + background-color: beige; +} + +.info-pane-element { + display: block; + left: 0; + right: 0; + padding: 1em; +} + +section#user_info { + position: relative; + top: 0; + background-color: aquamarine; + font-size: 18pt; + text-align: center; +} + +section#action_panel { + background-color: antiquewhite; +} + +iframe#content_frame { + display: block; + width: 100%; + height: 100%; + border: 0 transparent; +} + +section#table_section { + position: sticky; + height: calc(100vh - 4em); + overflow-y: auto; +} + +table.management { + width: 100%; + border-collapse: collapse; +} + +table.management thead th { + padding: 0.5em 0; + position: sticky; + top: 0; + background-color: darkgray; +} + +table.management tbody td { + border: 1pt lightgrey solid; + padding: 0.3em 0.5em; +} + +table.management tbody tr[highlight="true"] { + background-color: antiquewhite; +} + +table.management tbody tr[highlight="false"] td input { + pointer-events: none; +} + +section#user_manager_action_bar { + margin-top: 0.5em; +} + +section.window { + position: fixed; + display: block; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(128, 128, 128, 0.7); + padding: 4em; +} + +section.window-inner { + margin: 0 auto; + display: block; + border: 2pt solid darkslategray; + background-color: whitesmoke; + padding: 1em; + width: fit-content; +} + +td.checkbox { + width: 0; +} + +.hintbox-window { + position: fixed; + display: block; + width: 15em; + height: 8em; +} + +/* ----- */ + +section#game_list_panel { + display: block; + position: absolute; + left: 0; + right: 0; +} + +section.group-box { + margin: 1em; + left: 0; + right: 0; + background-color: beige; +} + +span.group-box-caption { + display: block; + left: 0; + right: 0; + padding: 1em; + background-color: chartreuse; +} + +section.group-box-inner { + display: inline-table; + padding: 1em; + collapse: 1em; + border-spacing: 1em; +} + +section.game-box { + display: table-cell; + width: 8em; + height: 8em; + border: 2pt dashed gray; + text-align: center; + vertical-align: middle; + cursor: pointer; +} + +section#test_area { + display: block; + position: absolute; + left: 0; + right: 0; +} + +section.challenge { + +} + +span.question { + +} + +section.answer-container { + +} + +section.answer { + +} \ No newline at end of file diff --git a/testground.php b/testground.php new file mode 100644 index 0000000..f0beeed --- /dev/null +++ b/testground.php @@ -0,0 +1,31 @@ + + + + + + + SpreadQuiz + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/testmgr.php b/testmgr.php new file mode 100644 index 0000000..5215d80 --- /dev/null +++ b/testmgr.php @@ -0,0 +1,93 @@ + false]); + +const TEST_ONGOING = "ongoing"; +const TEST_CONCLUDED = "concluded"; + +function create_or_continue_test(string $gameid, string $nickname): string +{ + global $testdb; + + // get game and user data + $game_data = get_game($gameid); + $user_data = get_user($nickname); + if ((count($game_data) === 0) || (count($user_data) === 0)) { + return ""; + } + + // check if this user has permission to take this test + // if the intersection of user's groups and game's assigned groups is zero, then the user has no access to this game + if (count(array_intersect($game_data["groups"], $user_data["groups"])) === 0) { + return ""; + } + + // check if the user had taken this test before + $fetch_criteria = [["gameid", "=", $gameid], "AND", ["nickname", "=", $nickname]]; + $previous_tests = $testdb->findBy($fetch_criteria); + if (count($previous_tests) > 0) { // if there are previous attempts, then... + // update timed tests to see if they had expired + update_timed_tests($previous_tests); + + // re-fetch tests, look only for ongoing + $ongoing_tests = $testdb->findBy([$fetch_criteria, "AND", ["state", "=", TEST_ONGOING]]); + if (count($ongoing_tests) !== 0) { // if there's an ongoing test + return $ongoing_tests[0]["_id"]; + } else { // there's no ongoing test + if ($game_data["properties"]["repeatable"]) { // test is repeatable... + return create_test($game_data, $user_data); + } else { // test is non-repeatable, cannot be attempted more times + return ""; + } + } + } else { // there were no previous attempts + return create_test($game_data, $user_data); + } +} + +function create_test(array $game_data, array $user_data) : string +{ + $gameid = $game_data["_id"]; + + // fill basic data + $game_data = [ + "gameid" => $gameid, + "nickname" => $user_data["nickname"], + ]; + + // fill challenges + $challenges = load_challenges($gameid); + + // shuffle answers + foreach ($challenges as &$ch) { + shuffle($ch["answers"]); + } + + $game_data["challenges"] = $challenges; + + // involve properties +} + +function update_timed_tests(array $test_data_array) +{ + $now = time(); + foreach ($test_data_array as $test_data) { + // look for unprocessed expired tests + if (($test_data["state"] === TEST_ONGOING) && ($test_data["end_limit_time"] < $now)) { + $test_data["state"] = TEST_CONCLUDED; + update_test($test_data); + } + } +} + +function update_test(array $test_data) +{ + global $testdb; + $testdb->update($test_data); +} \ No newline at end of file diff --git a/user_manager_frame.php b/user_manager_frame.php new file mode 100644 index 0000000..1f78457 --- /dev/null +++ b/user_manager_frame.php @@ -0,0 +1,104 @@ + + + + + + SpreadQuiz + + + + + + + + + +
+ + + + + + + + + + + + (üres)"; + // } + // return ""; + // } + // + // function create_table_row(array $keys, array $record) + // { + // $tr = ""; + // foreach ($keys as $k) { + // $tr .= ""; + // } + // $tr .= ""; + // return $tr; + // } + // + // $users = get_all_users(); + // foreach ($users as $u) { + // $nickname = $u["nickname"]; + // $tr = ""; + // $tr .= create_cell("", "checkbox"); + // $tr .= create_cell($u["nickname"]); + // $tr .= create_cell($u["realname"]); + // $tr .= create_cell(implode(",", $u["groups"])); + // $tr .= create_cell($u["privilege"]); + // $tr .= ""; + // echo $tr; + // } + // ?> + +
FelhasználónévNévCsoportokJogosultság
$content
${record[$k]}
+
+
+ + + +
+
+
+
+ Felhasználónév:
+ Teljes név:
+ Jelszó:
+ Csoportok:
+ Jogosultság: + + +
+ +
+
+ + + + + + + diff --git a/usermgr.php b/usermgr.php new file mode 100644 index 0000000..1062229 --- /dev/null +++ b/usermgr.php @@ -0,0 +1,113 @@ + false]); + +const PRIVILEGE_PLAYER = "player"; +const PRIVILEGE_CREATOR = "creator"; +const PRIVILEGE_QUIZMASTER = "admin"; // TODO: refactor! + +function add_user(string $nickname, string $password, string $realname, array $groups = [], string $privilege = PRIVILEGE_PLAYER): bool +{ + global $userdb; + if (count(get_user($nickname)) != 0) { // user exists + return false; + } + + $user_data = [ + "nickname" => $nickname, + "password" => password_hash($password, PASSWORD_DEFAULT), + "realname" => $realname, + "groups" => $groups, + "privilege" => $privilege + ]; + $userdb->insert($user_data); + return true; // user registration successful +} + +function delete_user(string $nickname) +{ + global $userdb; + if ($nickname == QUIZMASTER_NICKNAME) { + return; + } + + $user_data = get_user($nickname); + if (count($user_data) !== 0) { + foreach ($user_data["groups"] as $groupid) { + change_group_user_assignments($groupid, null, $nickname); + } + $userdb->deleteBy(["nickname", "=", $nickname]); + } +} + +function get_user(string $nickname): array +{ + global $userdb; + $user_data_array = $userdb->findBy(["nickname", "=", $nickname]); + return count($user_data_array) != 0 ? $user_data_array[0] : []; +} + +function update_user(array $user_data) +{ + global $userdb; + return $userdb->update($user_data); +} + +function change_password(string $nickname, string $old, string $new): bool +{ + $user_data = get_user($nickname); + if (count($user_data) != 0) { + if (password_verify($old, $user_data["password"])) { + $user_data["password"] = password_hash($new, PASSWORD_DEFAULT); + update_user($user_data); + return true; + } + } + return false; +} + +function change_user_group_assignments(string $nickname, $groupname_add, $groupname_remove) +{ + $user_data = get_user($nickname); + if (count($user_data) != 0) { + alter_array_contents($user_data["groups"], $groupname_add, $groupname_remove); + update_user($user_data); // update user + } +} + +function change_privilege_level(string $nickname, string $privilege) +{ + $user_data = get_user($nickname); + if (count($user_data) != 0) { + $user_data["privilege"] = $privilege; + update_user($user_data); + } +} + +function check_user_credentials(string $nickname, string $password): bool +{ + $user_data = get_user($nickname); + if (count($user_data) != 0) { + return password_verify($password, $user_data["password"]); + } else { + return false; + } +} + +function get_all_users(): array +{ + global $userdb; + return $userdb->findAll(); +} + +function get_all_nicknames() : array { + $nicknames = []; + $user_data_array = get_all_users(); + foreach ($user_data_array as $user_data) { + $nicknames[] = $user_data["nickname"]; + } + return $nicknames; +} \ No newline at end of file