diff --git a/autologin.php b/autologin.php index ec79d43..3c0f0e8 100644 --- a/autologin.php +++ b/autologin.php @@ -1,14 +1,17 @@ getUser($_SESSION["nickname"]); + $auto_logged_in = $autologin_user !== null; + $autologin_user_data = $autologin_user->toArray(["password"]); } function get_autologin_state() : bool { diff --git a/class/ExpressionBuilder.php b/class/ExpressionBuilder.php index 2ceb7db..cfedfec 100644 --- a/class/ExpressionBuilder.php +++ b/class/ExpressionBuilder.php @@ -3,7 +3,7 @@ class ExpressionBuilder { // Automatic typecast. - static function automatic_typecast(string $rval) : mixed + static function automaticTypecast(string $rval) : mixed { if (is_numeric($rval)) { // is it a numeric value? if (((int)$rval) == ((double)$rval)) { // is it an integer? @@ -37,11 +37,11 @@ class ExpressionBuilder foreach ($elements as $element) { // insert array elements $element = trim($element); if ($element !== "") { - $right[] = automatic_typecast($element); + $right[] = self::automaticTypecast($element); } } } else { // it must be a single value - $right = automatic_typecast($right); + $right = self::automaticTypecast($right); } return [$left, $op, $right]; @@ -102,9 +102,9 @@ class ExpressionBuilder // add subcriterion if ($subfilt[0] === "(") { - $criteria[] = build_query($subfilt); + $criteria[] = self::buildQuery($subfilt); } else { - $criteria[] = split_criterion($subfilt); + $criteria[] = self::splitCriterion($subfilt); } // add operator diff --git a/class/GameMgr.php b/class/GameMgr.php index 2a56395..de48a56 100644 --- a/class/GameMgr.php +++ b/class/GameMgr.php @@ -29,13 +29,20 @@ class Game extends AutoStoring // ------- + static private function genPublicId(): string + { + return uniqid("p"); + } + + // ------- + static private function patchUpGameDate(array &$a) : void { $version = $a["version"] ?? 0; if ($version < 2) { // update to game version 2 if (!key_exists("public_id", $a)) { $a["public"] = false; - $a["public_id"] = generate_public_id(); + $a["public_id"] = self::genPublicId(); } $a["version"] = 2; @@ -340,13 +347,6 @@ class GameMgr // -------- - static private function genPublicId(): string - { - return uniqid("p"); - } - - // -------- - function __construct() { $this->db = new \SleekDB\Store(GAMEDB, DATADIR, ["timeout" => false]); diff --git a/class/GroupMgr.php b/class/GroupMgr.php index 36cd02d..2b46fa8 100644 --- a/class/GroupMgr.php +++ b/class/GroupMgr.php @@ -2,6 +2,8 @@ require_once "AutoStoring.php"; +require_once "privilege_levels.php"; + class Group extends AutoStoring { private int $_id; // Group's ID (assigned by SleekDB) diff --git a/class/ReqHandler.php b/class/ReqHandler.php index a588555..a47a587 100644 --- a/class/ReqHandler.php +++ b/class/ReqHandler.php @@ -1,5 +1,6 @@ false]); - -const DEFAULT_GAME_PROPERTIES = [ - "forward_only" => false, // player may traverse back and forth between challenges - "time_limit" => 0, // no time limit; otherwise, this field indicates time limit in seconds - "repeatable" => false // this test can be taken multiple times -]; - -const CURRENT_GAME_VERSION = 2; // MUST BE INCREMENTED!! - -function generate_public_id(): string -{ - return uniqid("p"); -} - -function create_game(string $name, string $owner, string $description = "", array $properties = DEFAULT_GAME_PROPERTIES, array $contributors = [], array $challenges = []): bool -{ - global $gamedb; - $game_data = [ - "name" => $name, - "owner" => $owner, - "contributors" => $contributors, - "description" => $description, - "game_file_present" => false, - "properties" => $properties, - "groups" => [], - "public" => false, - "public_id" => generate_public_id(), - "version" => CURRENT_GAME_VERSION - ]; - $game_data = $gamedb->insert($game_data); - - // prepare game context - $id = $game_data["_id"]; - $current_game_media_dir = get_game_dir_by_gameid($id); - mkdir($current_game_media_dir); - save_challenges($id, []); - return true; -} - -function get_game(string $gameid): array -{ - global $gamedb; - return $gamedb->findById($gameid); -} - -function get_public_game(string $public_id): array -{ - global $gamedb; - return $gamedb->findBy([["public", "=", "true"], "AND", ["public_id", "=", $public_id]]); -} - -function update_game(array $game_data) -{ - global $gamedb; - $gamedb->update($game_data); -} - -function delete_game(string $gameid) -{ - global $gamedb; - $game_data = get_game($gameid); - if (count($game_data) != 0) { - foreach ($game_data["groups"] as $groupid) { - change_group_game_assignments($groupid, null, $gameid); - } - $gamedb->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 $gamedb; - return $gamedb->findAll(); -} - -function patch_up_game_data(array &$game_data) -{ - $game_version = $game_data["version"] ?? 0; - if ($game_version < 2) { // update to game version 2 - if (!key_exists("public_id", $game_data)) { - $game_data["public"] = false; - $game_data["public_id"] = generate_public_id(); - } - - $game_data["version"] = 2; - } - - return $game_version < CURRENT_GAME_VERSION; -} - -function get_all_game_data_by_contributor_nickname(string $nickname): array -{ - global $gamedb; - $game_headers = []; - if ($nickname !== "*") { - $game_data_array = $gamedb->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]); - } else { - $game_data_array = $gamedb->findAll(); - } - foreach ($game_data_array as $game_data) { - if (patch_up_game_data($game_data)) { - update_game($game_data); - } - $game_headers[] = $game_data; - } - return $game_headers; -} - -const CSV_ENCODINGS = ["UTF-8", "Windows-1252"]; - -function import_challenges_from_csv(string $csv_path, string $gameid): array -{ - $game_data = get_game($gameid); - $game_dir = get_game_dir_by_gameid($gameid); - if (count($game_data) === []) { - return ["n" => 0, "encoding" => ""]; - } - - // convert text encoding into UTF-8 - $data = file_get_contents($csv_path); - $encoding = "UNKNOWN"; - foreach (CSV_ENCODINGS as $enc) { // detect encoding - if (mb_check_encoding($data, $enc)) { - $encoding = $enc; - break; - } - } - - if ($encoding !== "UNKNOWN") { // if encoding has been detected successfully - $data = mb_convert_encoding($data, "UTF-8", $encoding); - file_put_contents($csv_path, $data); - } - - // load challenges - $challenges = []; - - // load filled CSV file - $f = fopen($csv_path, "r"); - if (!$f) { // failed to open file - return ["n" => 0, "encoding" => $encoding]; - } - while ($csvline = fgetcsv($f)) { - // skip empty lines - if (trim(implode("", $csvline)) === "") { - continue; - } - if (count($csvline) >= 3) { - // construct challenge record - $ch = [ - "question" => trim($csvline[0]), - "image_url" => trim($csvline[1]), - "correct_answer" => trim($csvline[2]), - "answers" => array_filter(array_slice($csvline, 2), function ($v) { - return trim($v) !== ""; - }) - ]; - - // if image is attached to the challenge, then give a random name to the image - if ($ch["image_url"] !== "") { - $old_img_name = $ch["image_url"]; - $ext = pathinfo($old_img_name, PATHINFO_EXTENSION); - $ext = ($ext !== "") ? ("." . $ext) : $ext; - $new_img_name = uniqid("img_", true) . $ext; - $ch["image_url"] = $new_img_name; - - // rename the actual file - $old_img_path = $game_dir . DIRECTORY_SEPARATOR . $old_img_name; - $new_img_path = $game_dir . DIRECTORY_SEPARATOR . $new_img_name; - rename($old_img_path, $new_img_path); - } - - // store the challenge - $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); - - return ["n" => count($challenges), "encoding" => $encoding]; -} - -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; -} - -function get_game_dir_by_gameid(string $gameid) -{ - return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $gameid; -} \ No newline at end of file diff --git a/globals.php b/globals.php index f5d007e..9dc150e 100644 --- a/globals.php +++ b/globals.php @@ -15,9 +15,9 @@ const LOGIN_URL = "login.php"; const MAIN_URL = "main.php"; const SESSION_NAME = "spreadquiz_sid"; const MAINTENANCE_FLAG_FILE = "MAINTENANCE"; - const MISSING_IMAGE_PLACEHOLDER = "media/image-missing_120px.png"; + session_name(SESSION_NAME); // autoload session diff --git a/groupmgr.php b/groupmgr.php deleted file mode 100644 index 532920e..0000000 --- a/groupmgr.php +++ /dev/null @@ -1,193 +0,0 @@ - false]); - -function create_group(string $groupname, string $owner, string $description = ""): bool -{ - global $groupdb; - - // test name uniqueness - $unique = manage_unique_in_siblings(0, $groupname); - - // initialize group data - $group_data = [ - "groupname" => $groupname, - "unique" => $unique, - "owner" => $owner, - "description" => $description, - "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 manage_unique_in_siblings(int $current_gid, $groupname): bool -{ - // make test on name uniqueness - global $groupdb; - $twins = $groupdb->findBy([["groupname", "=", "$groupname"], "AND", ["_id", "!=", $current_gid]]); - $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/install.php b/install.php index 20c089e..78f250b 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,5 @@ addUser(QUIZMASTER_NICKNAME, $pw, ""); $qm = $usrmgr->getUser(QUIZMASTER_NICKNAME); $qm->setPrivilege(PRIVILEGE_QUIZMASTER); diff --git a/interface.php b/interface.php index 240d785..aa1f469 100644 --- a/interface.php +++ b/interface.php @@ -1,9 +1,11 @@ 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)) { +if ((session_status() != PHP_SESSION_ACTIVE) || (!isset($_SESSION["nickname"])) || ($userMgr->getUser($_SESSION["nickname"]) === null)) { goto process_and_print; } @@ -470,7 +472,7 @@ function export_game_file_csv(ReqHandler &$rh, array $params): string $f = tmpfile(); header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename=\"challenges_$gameid.csv\"\r\n"); - export_challenges_to_csv($f, $gameid); + $game->exportChallengesToCSV($f); fseek($f, 0); fpassthru($f); } diff --git a/main.php b/main.php index 8dfccb9..224cc9d 100644 --- a/main.php +++ b/main.php @@ -1,6 +1,7 @@ getGame($game_id)->isUserContributorOrOwner($user_data["nickname"]) && ($user_data["privilege"] !== PRIVILEGE_QUIZMASTER)) { exit(); } diff --git a/testmgr.php b/testmgr.php deleted file mode 100644 index 133214e..0000000 --- a/testmgr.php +++ /dev/null @@ -1,415 +0,0 @@ - false]); - -const TEST_ONGOING = "ongoing"; -const TEST_CONCLUDED = "concluded"; - -function create_or_continue_test(string $gameid, string $nickname): string -{ - global $testdb; - - // check if user has access to the specific game - if (!does_user_access_game($nickname, $gameid)) { - return ""; - } - - // fetch game data - $game_data = get_game($gameid); - $user_data = get_user($nickname); - - // check if the user had taken this test before - $fetch_criteria = [["gameid", "=", (int)$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 -{ - global $testdb; - $gameid = $game_data["_id"]; - $game_properties = $game_data["properties"]; - - // fill basic data - $test_data = [ - "gameid" => $gameid, - "nickname" => $user_data["nickname"], - "gamename" => $game_data["name"] - ]; - - // fill challenges - $challenges = load_challenges($gameid); - - // shuffle answers - foreach ($challenges as &$ch) { - shuffle($ch["answers"]); - $ch["correct_answer"] = ""; - $ch["player_answer"] = ""; - } - - // involve properties - $now = time(); - $properties = [ - "state" => TEST_ONGOING, - "time_limited" => (($game_properties["time_limit"] ?: -1) > -1), - "start_time" => $now, - "repeatable" => $game_properties["repeatable"] ?: false - ]; - if ($properties["time_limited"]) { - $properties["end_limit_time"] = $now + $game_properties["time_limit"]; - } - - // merge properties and test data - $test_data = array_merge($test_data, $properties); - - // add challenges - $test_data["challenges"] = $challenges; - - // store game - $test_data = $testdb->insert($test_data); - - $testid = $test_data["_id"]; - return $testid; -} - -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["time_limited"]) && ($test_data["end_limit_time"] < $now)) { - conclude_test($test_data["_id"]); - } - } -} - -function update_test(array $test_data) -{ - global $testdb; - $testdb->update($test_data); -} - -function get_test(string $testid): array -{ - global $testdb; - return $testdb->findById($testid); -} - -function save_answer(string $testid, $chidx, $ansidx) -{ - $test_data = get_test($testid); - $chidx = (int)$chidx; - if ((count($test_data) > 0) && ($test_data["state"] === TEST_ONGOING)) { - if ($chidx < count($test_data["challenges"])) { - $test_data["challenges"][$chidx]["player_answer"] = $ansidx; - update_test($test_data); - } - } -} - -function get_concluded_tests(string $gameid, string $nickname) -{ - global $testdb; - $fetch_criteria = [["gameid", "=", (int)$gameid], "AND", ["nickname", "=", $nickname], "AND", ["state", "=", TEST_CONCLUDED]]; - $test_data_array = $testdb->findBy($fetch_criteria); - return $test_data_array; -} - -function conclude_test(string $testid) -{ - $test_data = get_test($testid); - if (count($test_data) === 0) { - return; - } - - // load game data - //$game_data = get_game($test_data["gameid"]); - $game_challenges = load_challenges($test_data["gameid"]); - - // check the answers - $challenge_n = count($test_data["challenges"]); // number of challenges - $cans_n = 0; // number of correct answers - for ($chidx = 0; $chidx < $challenge_n; $chidx++) { - // get challenge - $tch = &$test_data["challenges"][$chidx]; - $gch = $game_challenges[$chidx]; - - // translate correct answer into an index by the shuffled answer order - $cans_idx = array_search($gch["correct_answer"], $tch["answers"]); - $tch["correct_answer"] = $cans_idx; - - // check the player's answer - $player_answer = trim($tch["player_answer"]); - if (($player_answer !== "") && ($cans_idx === (int)$player_answer)) { - $cans_n++; - } - } - - // set state and fill summary - $test_data["state"] = TEST_CONCLUDED; - $test_data["end_time"] = time(); - $test_data["summary"] = [ - "challenge_n" => $challenge_n, - "correct_answer_n" => $cans_n - ]; - - update_test($test_data); -} - -function automatic_typecast(string $rval) -{ - if (is_numeric($rval)) { // is it a numeric value? - if (((int)$rval) == ((double)$rval)) { // is it an integer? - return (int)$rval; - } else { // is it a float? - return (double)$rval; - } - } else { // it's a string - return substr($rval, 1, strlen($rval) - 2); // strip leading and trailing quotes - } -} - -function split_criterion(string $crstr): array -{ - preg_match("/([<>=!]+|LIKE|NOT LIKE|IN|NOT IN|CONTAINS|NOT CONTAINS|BETWEEN|NOT BETWEEN|EXISTS)/", $crstr, $matches, PREG_OFFSET_CAPTURE); - - // extract operator - $op = $matches[0][0]; - $op_pos = $matches[0][1]; - - // extract operands - $left = trim(substr($crstr, 0, $op_pos)); - $right = trim(substr($crstr, $op_pos + strlen($op), strlen($crstr))); - - // automatic type conversion - if (str_starts_with($right, "[") && str_ends_with($right, "]")) { // is it an array? - $right = substr($right, 1, -1); // strip leading and trailing brackets - $elements = explode(",", $right); // extract array elements - $right = []; // re-init right value, since it's an array - foreach ($elements as $element) { // insert array elements - $element = trim($element); - if ($element !== "") { - $right[] = automatic_typecast($element); - } - } - } else { // it must be a single value - $right = automatic_typecast($right); - } - - return [$left, $op, $right]; -} - -function build_query(string $filter): array -{ - // skip empty filter processing - if (trim($filter) === "") { - return []; - } - - // subfilters and operations - $subfilts = []; - $operations = []; - - // buffer and scoring - $k = 0; - $k_prev = 0; - $buffer = ""; - - for ($i = 0; $i < strlen($filter); $i++) { - $c = $filter[$i]; - - // extract groups surrounded by parantheses - if ($c === "(") { - $k++; - } elseif ($c === ")") { - $k--; - } - - // only omit parentheses at the top-level expression - if (!((($c === "(") && ($k === 1)) || (($c === ")") && ($k === 0)))) { - $buffer .= $c; - } - - // if k = 0, then we found a subfilter - if (($k === 0) && ($k_prev === 1)) { - $subfilts[] = trim($buffer); - $buffer = ""; - } elseif (($k === 1) && ($k_prev === 0)) { - $op = trim($buffer); - if ($op !== "") { - $operations[] = $op; - } - $buffer = ""; - } - - // save k to be used next iteration - $k_prev = $k; - } - - // decide, whether further expansion of condition is needed - $criteria = []; - for ($i = 0; $i < count($subfilts); $i++) { - $subfilt = $subfilts[$i]; - - // add subcriterion - if ($subfilt[0] === "(") { - $criteria[] = build_query($subfilt); - } else { - $criteria[] = split_criterion($subfilt); - } - - // add operator - if (($i + 1) < count($subfilts)) { - $criteria[] = $operations[$i]; - } - } - - return $criteria; -} - -function build_ordering(string $orderby): array -{ - // don't process empty order instructions - if ($orderby === "") { - return []; - } - - // explode string at tokens delimiting separate order criteria - $ordering = []; - $subcriteria = explode(";", $orderby); - foreach ($subcriteria as $subcriterion) { - $parts = explode(":", $subcriterion); // fetch parts - $field_name = trim($parts[0], "\ \n\r\t\v\0\"'"); // strip leading and trailing quotes if exists - $direction = strtolower(trim($parts[1])); // fetch ordering direction - $ordering[$field_name] = $direction; // build ordering instruction - } - - return $ordering; -} - -function get_results_by_gameid(string $gameid, string $filter, string $orderby, bool $exclude_challenge_data): array -{ - global $testdb; - $qb = $testdb->createQueryBuilder(); - $qb = $qb->where(["gameid", "=", (int)$gameid]); - - // filtering - if (trim($filter) !== "") { - - // auto complete starting and ending parenthesis - if (!str_starts_with($filter, "(")) { - $filter = "(" . $filter; - } - if (!str_ends_with($filter, ")")) { - $filter = $filter . ")"; - } - - $criteria = build_query($filter); - $qb->where($criteria); - } - - // ordering - if (trim($orderby) !== "") { - $ordering = build_ordering($orderby); - $qb->orderBy($ordering); - } - - // excluding challenge data - if ($exclude_challenge_data) { - $qb->except(["challenges"]); - } - - $test_data_array = $qb->getQuery()->fetch(); - return $test_data_array; -} - -function generate_detailed_stats(string $gameid, array $testids): array -{ - if ((count($testids) === 0) || ($gameid === "")) { - return []; - } - - global $testdb; - - // fetch relevant entries - $qb = $testdb->createQueryBuilder(); - $criteria = [["gameid", "=", (int)$gameid], "AND", ["state", "=", "concluded"], "AND", ["_id", "IN", $testids]]; - $qb->where($criteria); - $qb->select(["challenges"]); - $entries = $qb->getQuery()->fetch(); - - $challenge_indices = []; - - // count answers - $aggregated = []; - foreach ($entries as $entry) { - foreach ($entry["challenges"] as $challenge) { - $correct_answer = $challenge["answers"][$challenge["correct_answer"]]; - $compound = $challenge["question"] . $correct_answer . count($challenge["answers"]) . $challenge["image_url"]; - $idhash = md5($compound); - - // if this is a new challenge to the list... - if (!isset($challenge_indices[$idhash])) { - $challenge_indices[$idhash] = count($challenge_indices); - $challenge_info = [ // copy challenge info - "hash" => $idhash, - "image_url" => $challenge["image_url"], - "question" => $challenge["question"], - "answers" => $challenge["answers"], - "correct_answer" => $correct_answer, - "player_answers" => array_fill(0, count($challenge["answers"]), 0), - "answer_count" => count($challenge["answers"]), - ]; - $aggregated[$challenge_indices[$idhash]] = $challenge_info; // insert challenge info - } - - // fetch challenge index - $challenge_idx = $challenge_indices[$idhash]; - - // add up player answer - $answer_idx = array_search($challenge["answers"][$challenge["player_answer"]], $aggregated[$challenge_idx]["answers"]); // transform player answer index to report answer index - $aggregated[$challenge_idx]["player_answers"][(int)$answer_idx]++; - } - } - - // produce derived info - foreach ($aggregated as &$entry) { - $entry["answer_ratio"] = $entry["player_answers"]; - $answer_n = count($entry["answer_ratio"]); - $sum = array_sum($entry["player_answers"]); - - if ($sum === 0) { - continue; - } - - for ($i = 0; $i < $answer_n; $i++) { - $entry["answer_ratio"][$i] = $entry["player_answers"][$i] / $sum; - } - } - - // match challenges - return $aggregated; -} \ No newline at end of file diff --git a/usermgr.php b/usermgr.php deleted file mode 100644 index 9d3fe54..0000000 --- a/usermgr.php +++ /dev/null @@ -1,119 +0,0 @@ - false]); - -const PRIVILEGE_NONE = "none"; -const PRIVILEGE_PLAYER = "player"; -const PRIVILEGE_CREATOR = "creator"; -const PRIVILEGE_QUIZMASTER = "admin"; // TODO: refactor! - -//function add_user(string $nickname, string $password, string $realname, array $groupids = [], 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" => $groupids, -// "privilege" => $privilege -// ]; -// foreach ($groupids as $groupid) { -// change_group_user_assignments($groupid, $nickname, null); -// } -// $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