- automatic checking of publishing conditions

- result analyzer with basic functionality added
This commit is contained in:
Wiesner András 2024-09-07 18:25:14 +02:00
parent 2fd08094a6
commit fa74f65847
9 changed files with 325 additions and 21 deletions

View File

@ -92,6 +92,14 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
<input type="text" pattern="(([01][0-9])|([2][0-3])):[0-5][0-9]:[0-5][0-9]" id="time_limit">
</td>
</tr>
<tr>
<td>
<label for="repeatable">Megismételhető:</label>
</td>
<td>
<input type="checkbox" id="repeatable">
</td>
</tr>
<tr>
<td>
<label for="public">Publikus:</label>
@ -101,20 +109,13 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
<input type="text" id="public_url" readonly>
</td>
</tr>
<tr>
<td>
<label for="repeatable">Megismételhető:</label>
</td>
<td>
<input type="checkbox" id="repeatable">
</td>
</tr>
<tr>
<td>
Eredmények:
</td>
<td>
<input type="button" value="Mutat" onclick="list_results_by_game(EDITED_GAME)">
<input type="button" value="Gyors áttekintés" onclick="list_results_by_game(EDITED_GAME)">
<input type="button" value="Részletes megjelenítés" onclick="window.open(`result_analyzer.php?game_id=${EDITED_GAME._id}`)">
</td>
</tr>
</table>

View File

@ -11,6 +11,13 @@ const DEFAULT_GAME_PROPERTIES = [
"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;
@ -23,7 +30,8 @@ function create_game(string $name, string $owner, string $description = "", arra
"properties" => $properties,
"groups" => [],
"public" => false,
"public_id" => uniqid("p"),
"public_id" => generate_public_id(),
"version" => CURRENT_GAME_VERSION
];
$game_data = $gamedb->insert($game_data);
@ -41,6 +49,12 @@ function get_game(string $gameid): array
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;
@ -74,6 +88,21 @@ function get_all_games()
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;
@ -84,6 +113,9 @@ function get_all_game_data_by_contributor_nickname(string $nickname): array
$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;

View File

@ -221,7 +221,14 @@ switch ($action) {
$game_data["properties"]["time_limit"] = $properties["time_limit"];
$game_data["properties"]["repeatable"] = $properties["repeatable"];
$game_data["public"] = $data["public"];
// 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
@ -305,8 +312,10 @@ switch ($action) {
case "get_results_by_gameid":
{
$gameid = trim($_REQUEST["gameid"] ?: "");
$filter = trim($_REQUEST["filter"] ?: "");
if (($gameid !== "") && (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster)) {
$result = json_encode(get_results_by_gameid($gameid));
$game_results = get_results_by_gameid($gameid, $filter);
$result = json_encode($game_results);
}
}
break;

View File

@ -58,6 +58,20 @@ function create_edit_game(game = null) {
let publicChk = document.getElementById("public");
let publicUrlF = document.getElementById("public_url");
let hide_public_url_field = () => {
publicUrlF.hidden = !publicChk.checked;
}
let hide_public_option = () => {
let hide = (!repeatableChk.checked) || (time_limitedChk.checked);
if (hide) {
publicChk.checked = false;
}
publicChk.disabled = hide;
hide_public_url_field();
}
if (!updating) { // creating a new game
nameF.value = "";
descriptionF.value = "";
@ -82,9 +96,7 @@ function create_edit_game(game = null) {
contributorsF.value = game["contributors"].join(", ");
groupF.value = game["groups"].join(", ");
publicChk.addEventListener("change", () => {
publicUrlF.hidden = !publicChk.checked;
});
publicChk.addEventListener("change", hide_public_url_field);
publicChk.checked = game["public"];
publicUrlF.hidden = !publicChk.checked;
publicUrlF.value = game["public_id"];
@ -93,9 +105,13 @@ function create_edit_game(game = null) {
let time_limit = Math.max(Number(props["time_limit"]), 0);
time_limitF.value = seconds_to_time(time_limit);
time_limitedChk.checked = time_limit > 0;
time_limitedChk.addEventListener("change", hide_public_option);
handle_time_limit_chkbox();
repeatableChk.checked = props["repeatable"];
repeatableChk.addEventListener("change", hide_public_option);
hide_public_option();
// show additional controls
show("additional_controls");

58
js/result_analyzer.js Normal file
View File

@ -0,0 +1,58 @@
function create_cell(content) {
let cell = document.createElement("td");
cell.innerHTML = content;
return cell;
}
function fetch_results() {
let filterF = document.getElementById("filter");
let req = {action: "get_results_by_gameid", gameid: GAMEID, filter: filterF.value.trim()};
request(req).then(resp => {
let rd = document.getElementById("results_display");
let results = JSON.parse(resp);
let empty_resp = results.length === 0;
rd.innerHTML = empty_resp ? "Nincs találat." : "";
if (empty_resp) {
return;
}
// let n = results.length;
results.forEach((record) => {
let row = document.createElement("tr");
// is the game concluded
let concluded = record["state"] === "concluded";
let percentage = "-";
let timestamp = "-";
// replace some fields if game was concluded
if (concluded) {
// percentage
let summary = record["summary"];
let r = Math.floor((summary["correct_answer_n"] / summary["challenge_n"]) * 100);
percentage = `${r}%`;
// finish timestamp
timestamp = unix_time_to_human_readable(record["end_time"]);
}
// create cells
let empty_cell = create_cell("");
let name_cell = create_cell(record.nickname)
let percentage_cell = create_cell(percentage)
let timestamp_cell = create_cell(timestamp);
row.append(empty_cell, name_cell, percentage_cell, timestamp_cell);
// append row
rd.appendChild(row);
});
});
}

View File

@ -28,5 +28,15 @@ if (get_autologin_state()) {
<input type="button" value="Belépés" onclick="login()">
</section>
</section>
<script>
function login_at_pressing_enter(evt) {
if (evt.key === "Enter") {
login();
}
}
document.getElementById("nickname").addEventListener("keydown", login_at_pressing_enter);
document.getElementById("password").addEventListener("keydown", login_at_pressing_enter);
</script>
</body>
</html>

79
result_analyzer.php Normal file
View File

@ -0,0 +1,79 @@
<?php
require_once "globals.php";
require_once "autologin.php";
$user_data = get_autologin_user_data();
if (!get_autologin_state()) {
exit();
}
// fetch game ID; cannot continue without specifying it
$game_id = trim($_REQUEST["game_id"] ?: "");
if ($game_id === "") {
exit();
}
require_once "gamemgr.php";
// no user without access may tamper with the results
if (!is_user_contributor_to_game($game_id, $user_data["nickname"])) {
exit();
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'>
<title>SpreadQuiz - Eredménykezelő</title>
<script src="js/req.js"></script>
<script src="js/o.js"></script>
<script src="js/common.js"></script>
<script src="js/spreadquiz.js"></script>
<script src="js/result_analyzer.js"></script>
<link rel="stylesheet" href="style/spreadquiz.css">
<link rel="stylesheet" href="style/quizmaster_area.css"/>
</head>
<body>
<section style="margin-bottom: 0.3em">
<input type="text" placeholder="Szűrőfeltétel" id="filter" style="font-family: 'Monaco', monospace; width: 50em;">
<input type="button" value="Szűrés" onclick="fetch_results()">
</section>
<section>
<section id="table_section">
<table class="management">
<thead>
<tr>
<th></th>
<th>
Felhasználónév<br>
<code>
[nickname]
</code>
</th>
<th>Eredmény<br>
<code>
[result]
</code>
</th>
<th>Időbélyeg<br>
<code>
[timestamp]
</code>
</th>
</tr>
</thead>
<tbody id="results_display">
</tbody>
</table>
</section>
</section>
<script>
let GAMEID = <?="$game_id"?>;
</script>
</body>
</html>

View File

@ -9,10 +9,12 @@ if (!get_autologin_state() || !isset($_REQUEST["testid"])) {
}
$testid = trim($_REQUEST["testid"] ?: "");
if ($testid === "") {
exit();
}
?>
<!DOCTYPE html>

View File

@ -51,7 +51,7 @@ function create_or_continue_test(string $gameid, string $nickname): string
}
}
function create_test(array $game_data, array $user_data) : string
function create_test(array $game_data, array $user_data): string
{
global $testdb;
$gameid = $game_data["_id"];
@ -134,14 +134,16 @@ function save_answer(string $testid, $chidx, $ansidx)
}
}
function get_concluded_tests(string $gameid, string $nickname) {
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) {
function conclude_test(string $testid)
{
$test_data = get_test($testid);
if (count($test_data) === 0) {
return;
@ -181,9 +183,104 @@ function conclude_test(string $testid) {
update_test($test_data);
}
function get_results_by_gameid(string $gameid) : array {
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 (($right[0] !== "\"") && ($right[0] !== "\'")) { // numeric value
$right = (int) $right;
} else { // string value
$right = substr($right, 1, strlen($right) - 2); // strip leading and trailing quotes
}
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--;
} else {
$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 get_results_by_gameid(string $gameid, string $filter): array
{
global $testdb;
$fetch_criteria = ["gameid", "=", (int)$gameid];
$test_data_array = $testdb->findBy($fetch_criteria);
$qb = $testdb->createQueryBuilder();
$qb = $qb->where(["gameid", "=", (int)$gameid]);
if (trim($filter) !== "") {
$criteria = build_query($filter);
$qb->where($criteria);
}
$test_data_array = $qb->getQuery()->fetch();
return $test_data_array;
}