- report generation implemented
- recursive filtering fixed - grouping added
This commit is contained in:
parent
55a68138b5
commit
89ac41a6e6
@ -112,9 +112,9 @@ switch ($action) {
|
||||
}
|
||||
|
||||
// test-related queries
|
||||
if ((($testid = trim($_REQUEST["testid"] ?: "")) !== "") &&
|
||||
if (isset($_REQUEST["testid"]) && (($testid = trim($_REQUEST["testid"])) !== "") &&
|
||||
((count($test_data = get_test($testid))) > 0) &&
|
||||
($test_data["nickname"] === $nickname)) {
|
||||
(($test_data["nickname"] === $nickname) || $is_quizmaster)) {
|
||||
|
||||
// update the test if timed
|
||||
update_timed_tests([$test_data]);
|
||||
@ -141,6 +141,9 @@ if ((($testid = trim($_REQUEST["testid"] ?: "")) !== "") &&
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case "save_answer":
|
||||
{
|
||||
$chidx = $_REQUEST["challenge_index"];
|
||||
@ -313,12 +316,21 @@ switch ($action) {
|
||||
{
|
||||
$gameid = trim($_REQUEST["gameid"] ?: "");
|
||||
$filter = trim($_REQUEST["filter"] ?: "");
|
||||
$ordering = trim($_REQUEST["orderby"] ?: "");
|
||||
if (($gameid !== "") && (is_user_contributor_to_game($gameid, $nickname) || $is_quizmaster)) {
|
||||
$game_results = get_results_by_gameid($gameid, $filter);
|
||||
$game_results = get_results_by_gameid($gameid, $filter, $ordering, true);
|
||||
$result = json_encode($game_results);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "generate_detailed_stats":
|
||||
{
|
||||
$testids = json_decode(trim($_REQUEST["testids"] ?: "[]"), true);
|
||||
$gameid = trim($_REQUEST["gameid"] ?: "");
|
||||
$stats = generate_detailed_stats($gameid, $testids);
|
||||
$result = json_encode($stats);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// quizmaster actions
|
||||
|
15
js/common.js
15
js/common.js
@ -31,3 +31,18 @@ function time_to_seconds(t) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function preprocess_inserts(str) {
|
||||
let code_delim = '`';
|
||||
let parts = str.split(code_delim);
|
||||
let res = "";
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
res += parts[i];
|
||||
if (i % 2 === 0) {
|
||||
res += "<code>";
|
||||
} else {
|
||||
res += "</code>";
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
@ -339,6 +339,10 @@ function list_results_by_game(game) {
|
||||
}
|
||||
}
|
||||
|
||||
test_summary_record.addEventListener("click", () => {
|
||||
window.open(`testground.php?testid=${record["_id"]}&view_only=true`, "_blank");
|
||||
});
|
||||
|
||||
test_group_box.appendChild(test_summary_record);
|
||||
|
||||
n--;
|
||||
|
@ -1,4 +1,4 @@
|
||||
function create_cell(content) {
|
||||
function create_cell(content = "") {
|
||||
let cell = document.createElement("td");
|
||||
cell.innerHTML = content;
|
||||
return cell;
|
||||
@ -7,8 +7,14 @@ function create_cell(content) {
|
||||
function fetch_results() {
|
||||
|
||||
let filterF = document.getElementById("filter");
|
||||
let orderbyF = document.getElementById("orderby");
|
||||
|
||||
let req = {action: "get_results_by_gameid", gameid: GAMEID, filter: filterF.value.trim()};
|
||||
let req = {
|
||||
action: "get_results_by_gameid",
|
||||
gameid: GAMEID,
|
||||
filter: filterF.value.trim(),
|
||||
orderby: orderbyF.value.trim()
|
||||
};
|
||||
|
||||
request(req).then(resp => {
|
||||
let rd = document.getElementById("results_display");
|
||||
@ -28,8 +34,9 @@ function fetch_results() {
|
||||
// is the game concluded
|
||||
let concluded = record["state"] === "concluded";
|
||||
|
||||
let percentage = "-";
|
||||
let timestamp = "-";
|
||||
let percentage = "";
|
||||
let start_timestamp = unix_time_to_human_readable(record["start_time"]);
|
||||
let end_timestamp = "";
|
||||
|
||||
// replace some fields if game was concluded
|
||||
if (concluded) {
|
||||
@ -39,16 +46,31 @@ function fetch_results() {
|
||||
percentage = `${r}%`;
|
||||
|
||||
// finish timestamp
|
||||
timestamp = unix_time_to_human_readable(record["end_time"]);
|
||||
end_timestamp = unix_time_to_human_readable(record["end_time"]);
|
||||
}
|
||||
|
||||
// create cells
|
||||
let empty_cell = create_cell("");
|
||||
let selectChk = document.createElement("input");
|
||||
selectChk.type = "checkbox";
|
||||
selectChk.name = "game_select";
|
||||
selectChk.record = record;
|
||||
|
||||
let selection_cell = create_cell();
|
||||
selection_cell.append(selectChk);
|
||||
|
||||
let id_cell = create_cell(record["_id"]);
|
||||
|
||||
let inspect_link = `<a href="testground.php?testid=${record["_id"]}&view_only=true" target="_blank">🔎</a>`
|
||||
let inspect_cell = create_cell(inspect_link);
|
||||
let name_cell = create_cell(record.nickname)
|
||||
let percentage_cell = create_cell(percentage)
|
||||
let timestamp_cell = create_cell(timestamp);
|
||||
let start_timestamp_cell = create_cell(start_timestamp);
|
||||
let end_timestamp_cell = create_cell(end_timestamp);
|
||||
|
||||
row.append(empty_cell, name_cell, percentage_cell, timestamp_cell);
|
||||
row.append(selection_cell, id_cell, inspect_cell, name_cell, percentage_cell, start_timestamp_cell, end_timestamp_cell);
|
||||
|
||||
// save record data into the row object
|
||||
row.record = record;
|
||||
|
||||
// append row
|
||||
rd.appendChild(row);
|
||||
@ -56,3 +78,91 @@ function fetch_results() {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function generate_report() {
|
||||
let testids = [];
|
||||
let game_selectChks = document.getElementsByName("game_select");
|
||||
game_selectChks.forEach((chk) => {
|
||||
if (chk.checked) {
|
||||
testids.push(chk.record["_id"]);
|
||||
}
|
||||
});
|
||||
|
||||
let req = {
|
||||
action: "generate_detailed_stats",
|
||||
gameid: GAMEID,
|
||||
testids: JSON.stringify(testids)
|
||||
};
|
||||
|
||||
request(req).then((resp) => {
|
||||
let stats = JSON.parse(resp);
|
||||
let statsTab = window.open("report.html", "_blank");
|
||||
|
||||
statsTab.addEventListener("load", () => {
|
||||
let report_display = statsTab.document.getElementById("report_display");
|
||||
report_display.innerHTML = "";
|
||||
|
||||
stats.forEach((challenge) => {
|
||||
let challenge_box = document.createElement("section");
|
||||
challenge_box.classList.add("challenge");
|
||||
challenge_box.style.width = "100%";
|
||||
|
||||
let img_url = challenge["image_url"];
|
||||
if (img_url !== "") {
|
||||
let fig = document.createElement("img");
|
||||
fig.src = img_url;
|
||||
fig.classList.add("question-image");
|
||||
challenge_box.append(fig);
|
||||
}
|
||||
|
||||
let question = document.createElement("span");
|
||||
question.classList.add("question");
|
||||
question.innerHTML = preprocess_inserts(challenge["question"]);
|
||||
let answer_container = document.createElement("section");
|
||||
answer_container.classList.add("answer-container");
|
||||
challenge_box.append(question, answer_container);
|
||||
|
||||
let n = challenge["answer_count"];
|
||||
for (let i = 0; i < n; i++) {
|
||||
let answer = challenge["answers"][i];
|
||||
let correct_answer = answer === challenge["correct_answer"];
|
||||
|
||||
let answer_section = document.createElement("section");
|
||||
answer_section.classList.add("answer");
|
||||
|
||||
let progress_bar_container = document.createElement("section");
|
||||
progress_bar_container.classList.add("pb-container")
|
||||
let progress_bar_indicator = document.createElement("section");
|
||||
progress_bar_indicator.classList.add("pb-indicator")
|
||||
|
||||
let percentage = challenge["answer_ratio"][i] * 100;
|
||||
progress_bar_indicator.style.width = `${percentage}%`;
|
||||
progress_bar_indicator.innerText = Math.round(percentage * 100.0) / 100.0 + "%";
|
||||
progress_bar_indicator.setAttribute("correct", correct_answer ? "true" : "false");
|
||||
|
||||
progress_bar_container.append(progress_bar_indicator);
|
||||
|
||||
let answer_text = document.createElement("span");
|
||||
answer_text.classList.add("answer");
|
||||
answer_text.innerHTML = preprocess_inserts(answer);
|
||||
answer_text.setAttribute("correct", correct_answer ? "true" : "false");
|
||||
|
||||
answer_section.append(progress_bar_container, answer_text);
|
||||
|
||||
answer_container.append(answer_section);
|
||||
}
|
||||
|
||||
report_display.append(challenge_box);
|
||||
});
|
||||
|
||||
statsTab.MathJax.typeset();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggle_test_selection() {
|
||||
let game_selectChks = document.getElementsByName("game_select");
|
||||
game_selectChks.forEach((chk) => {
|
||||
chk.checked = !chk.checked;
|
||||
});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
let TEST_DATA = {}
|
||||
let INTERVAL_HANDLE = null;
|
||||
|
||||
function populate_infobox(test_data) {
|
||||
function populate_infobox(test_data, view_only) {
|
||||
if (INTERVAL_HANDLE !== null) {
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
}
|
||||
@ -12,6 +12,9 @@ function populate_infobox(test_data) {
|
||||
let durationS = document.getElementById("duration");
|
||||
let percentageS = document.getElementById("percentage");
|
||||
|
||||
let submitBtn = document.getElementById("submit_btn");
|
||||
submitBtn.hidden = view_only;
|
||||
|
||||
game_nameS.innerHTML = test_data["gamename"];
|
||||
|
||||
if (test_concluded) {
|
||||
@ -28,19 +31,27 @@ function populate_infobox(test_data) {
|
||||
hide("ongoing-info");
|
||||
show("concluded-info");
|
||||
} else {
|
||||
let timerS = document.getElementById("timer");
|
||||
let time_left_s = Number(test_data["end_limit_time"]) - Number(test_data["current_time"]);
|
||||
|
||||
let print_timer = () => {
|
||||
timerS.innerHTML = seconds_to_time(time_left_s);
|
||||
};
|
||||
|
||||
if (test_data["time_limited"]) {
|
||||
let timerS = document.getElementById("timer");
|
||||
let time_left_s = Number(test_data["end_limit_time"]) - Number(test_data["current_time"]);
|
||||
seconds_to_time(time_left_s);
|
||||
INTERVAL_HANDLE = setInterval(() => {
|
||||
time_left_s--;
|
||||
timerS.innerHTML = seconds_to_time(time_left_s);
|
||||
if (time_left_s <= 0) {
|
||||
populate_all(test_data["_id"]);
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
INTERVAL_HANDLE = null;
|
||||
}
|
||||
}, 1000);
|
||||
print_timer()
|
||||
|
||||
if (!view_only) {
|
||||
INTERVAL_HANDLE = setInterval(() => {
|
||||
time_left_s--;
|
||||
print_timer();
|
||||
if (time_left_s <= 0) {
|
||||
populate_all(test_data["_id"]);
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
INTERVAL_HANDLE = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
show("time-info");
|
||||
} else {
|
||||
hide("time-info");
|
||||
@ -59,7 +70,7 @@ function populate_infobox(test_data) {
|
||||
}
|
||||
}
|
||||
|
||||
function populate_challenges(test_data) {
|
||||
function populate_challenges(test_data, view_only = false) {
|
||||
let test_display = document.getElementById("test_display");
|
||||
test_display.innerHTML = "";
|
||||
|
||||
@ -99,7 +110,7 @@ function populate_challenges(test_data) {
|
||||
answer_radio.type = "radio";
|
||||
answer_radio.id = `${challenge_N}_${answer_N}`;
|
||||
answer_radio.name = `challenge_${challenge_N}`;
|
||||
answer_radio.disabled = test_concluded;
|
||||
answer_radio.disabled = test_concluded || view_only;
|
||||
let answer_N_snapshot = answer_N;
|
||||
answer_radio.addEventListener("input", () => {
|
||||
save_answer(challenge_N_snapshot, answer_N_snapshot);
|
||||
@ -132,15 +143,16 @@ function populate_challenges(test_data) {
|
||||
MathJax.typeset();
|
||||
}
|
||||
|
||||
function populate_all(test_id) {
|
||||
function populate_all(test_id, view_only) {
|
||||
let req = {
|
||||
action: "get_test",
|
||||
view_only: view_only,
|
||||
testid: test_id
|
||||
}
|
||||
request(req).then(resp => {
|
||||
TEST_DATA = JSON.parse(resp);
|
||||
populate_challenges(TEST_DATA);
|
||||
populate_infobox(TEST_DATA);
|
||||
populate_challenges(TEST_DATA, view_only);
|
||||
populate_infobox(TEST_DATA, view_only);
|
||||
});
|
||||
}
|
||||
|
||||
@ -154,21 +166,6 @@ function save_answer(chidx, aidx) {
|
||||
request(req);
|
||||
}
|
||||
|
||||
function preprocess_inserts(str) {
|
||||
let code_delim = '`';
|
||||
let parts = str.split(code_delim);
|
||||
let res = "";
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
res += parts[i];
|
||||
if (i % 2 === 0) {
|
||||
res += "<code>";
|
||||
} else {
|
||||
res += "</code>";
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function submit_test() {
|
||||
let req = {
|
||||
action: "submit_test",
|
||||
|
20
report.html
Normal file
20
report.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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 - Részletes jelentés</title>
|
||||
<script src="js/req.js"></script>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<script src="js/o.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<link rel="stylesheet" href="style/spreadquiz.css">
|
||||
<link rel="stylesheet" href="style/quizmaster_area.css"/>
|
||||
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section id="report_display">
|
||||
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -41,13 +41,23 @@ if (!is_user_contributor_to_game($game_id, $user_data["nickname"]) && ($user_dat
|
||||
|
||||
<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="text" placeholder="Rendezés" id="orderby" style="font-family: 'Monaco', monospace; width: 50em;">
|
||||
<input type="button" value="Szűrés" onclick="fetch_results()">
|
||||
<input type="button" value="Jelentés előállítása" onclick="generate_report()">
|
||||
</section>
|
||||
<section>
|
||||
<section id="table_section">
|
||||
<table class="management">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" onclick="toggle_test_selection()">
|
||||
</th>
|
||||
<th>#<br>
|
||||
<code>
|
||||
[_id]
|
||||
</code>
|
||||
</th>
|
||||
<th></th>
|
||||
<th>
|
||||
Felhasználónév<br>
|
||||
@ -60,11 +70,17 @@ if (!is_user_contributor_to_game($game_id, $user_data["nickname"]) && ($user_dat
|
||||
[result]
|
||||
</code>
|
||||
</th>
|
||||
<th>Időbélyeg<br>
|
||||
<th>Kezdés ideje<br>
|
||||
<code>
|
||||
[timestamp]
|
||||
[start_time]
|
||||
</code>
|
||||
</th>
|
||||
<th>Befejezés ideje<br>
|
||||
<code>
|
||||
[end_time]
|
||||
</code>
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="results_display">
|
||||
@ -74,6 +90,7 @@ if (!is_user_contributor_to_game($game_id, $user_data["nickname"]) && ($user_dat
|
||||
</section>
|
||||
<script>
|
||||
let GAMEID = <?="$game_id"?>;
|
||||
fetch_results();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -94,3 +94,38 @@ section.window-inner tr td:first-of-type {
|
||||
padding: 0.2em;
|
||||
color: #671b17;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
section.pb-container {
|
||||
display: inline-block;
|
||||
width: 8em;
|
||||
height: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
section.pb-indicator {
|
||||
height: 100%;
|
||||
background-color: #a1d7d7;
|
||||
border-radius: 0.3em;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
section.pb-indicator[correct=true] {
|
||||
background-color: #176767;
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
section#report_display {
|
||||
width: fit-content;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
span.answer[correct=true] {
|
||||
color: whitesmoke;
|
||||
background-color: #176767;
|
||||
border-radius: 0.3em;
|
||||
}
|
@ -9,6 +9,7 @@ if (!get_autologin_state() || !isset($_REQUEST["testid"])) {
|
||||
}
|
||||
|
||||
$testid = trim($_REQUEST["testid"] ?: "");
|
||||
$view_only = trim($_REQUEST["view_only"] ?: "false") === "true" ? "true" : "false";
|
||||
|
||||
if ($testid === "") {
|
||||
exit();
|
||||
@ -39,25 +40,31 @@ if ($testid === "") {
|
||||
<section id="infobox">
|
||||
<span id="game_name"></span>
|
||||
<section>
|
||||
<?php if ($view_only) { ?>
|
||||
<section style="margin-bottom: 1em">
|
||||
CSAK OLVASHATÓ,<br>
|
||||
NEM FRISSÜL AUTOMATIKUSAN!
|
||||
</section>
|
||||
<?php } ?>
|
||||
<section id="ongoing-info">
|
||||
<section id="time-info" shown="false">
|
||||
<span class="infobox-description">Hátralevő idő:</span>
|
||||
<section id="timer">10:00:00</section>
|
||||
<section id="timer">00:00:00</section>
|
||||
</section>
|
||||
<section id="further-info">
|
||||
|
||||
</section>
|
||||
<input type="button" value="Beküld" onclick="submit_test()">
|
||||
<input type="button" value="Beküld" onclick="submit_test()" id="submit_btn">
|
||||
</section>
|
||||
<section id="concluded-info">
|
||||
<section id="duration"></section>
|
||||
<span class="infobox-description">Eredmény:</span>
|
||||
<section id="percentage">95% (19/20)</section>
|
||||
<section id="percentage">100% (20/20)</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<script>
|
||||
populate_all("<?=$testid ?>");
|
||||
populate_all("<?=$testid ?>", <?=$view_only ?>);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
151
testmgr.php
151
testmgr.php
@ -183,7 +183,23 @@ function conclude_test(string $testid)
|
||||
update_test($test_data);
|
||||
}
|
||||
|
||||
function split_criterion(string $crstr): array {
|
||||
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;
|
||||
}
|
||||
} elseif (str_starts_with($rval, 'T')) { // is it a date/time value?
|
||||
return strtotime(substr($rval, 1)); // convert to UNIX timestamp
|
||||
} 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
|
||||
@ -195,10 +211,18 @@ function split_criterion(string $crstr): array {
|
||||
$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
|
||||
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];
|
||||
@ -228,7 +252,10 @@ function build_query(string $filter): array
|
||||
$k++;
|
||||
} elseif ($c === ")") {
|
||||
$k--;
|
||||
} else {
|
||||
}
|
||||
|
||||
// only omit parentheses at the top-level expression
|
||||
if (!((($c === "(") && ($k === 1)) || (($c === ")") && ($k === 0)))) {
|
||||
$buffer .= $c;
|
||||
}
|
||||
|
||||
@ -266,21 +293,129 @@ function build_query(string $filter): array
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $criteria;
|
||||
}
|
||||
|
||||
function get_results_by_gameid(string $gameid, string $filter): array
|
||||
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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user