- function doing report generation has been beautified

- report generation windows was added to game manager
- full TeX source downloading compressed into a ZIP
This commit is contained in:
Wiesner András 2024-09-28 13:23:08 +02:00
parent a01b1f1760
commit 1fff851fb5
9 changed files with 107 additions and 34 deletions

View File

@ -18,7 +18,8 @@ class TestSummary
private float $percentage; // Ratio of correct answers private float $percentage; // Ratio of correct answers
// Calculate percentage. // Calculate percentage.
private function calculatePercentage() : void { private function calculatePercentage(): void
{
if ($this->challengeN > 0) { if ($this->challengeN > 0) {
$this->percentage = $this->correctAnswerN / (double)$this->challengeN * 100.0; $this->percentage = $this->correctAnswerN / (double)$this->challengeN * 100.0;
} else { // avoid division by zero } else { // avoid division by zero
@ -288,12 +289,12 @@ class Test extends AutoStoring
return $this->gameId; return $this->gameId;
} }
public function isConcluded() : bool public function isConcluded(): bool
{ {
return $this->state === self::TEST_CONCLUDED; return $this->state === self::TEST_CONCLUDED;
} }
public function isOngoing() : bool public function isOngoing(): bool
{ {
return $this->state === self::TEST_ONGOING; return $this->state === self::TEST_ONGOING;
} }
@ -332,25 +333,28 @@ class TestMgr
} }
// Update test in the database. // Update test in the database.
function updateTest(Test &$test): void { function updateTest(Test &$test): void
{
$a = $test->toArray(); $a = $test->toArray();
$this->db->update($a); $this->db->update($a);
} }
// Add test to the database. // Add test to the database.
function addTest(Game &$game, User &$user): Test { function addTest(Game &$game, User &$user): Test
{
// create new test // create new test
$test = new Test($this,$game, $user); $test = new Test($this, $game, $user);
// insert into database // insert into database
$a = $test->toArray(["_id"]); $a = $test->toArray(["_id"]);
$a = $this->db->insert($a); $a = $this->db->insert($a);
$test = new Test($this,$a); $test = new Test($this, $a);
return $test; return $test;
} }
function addOrContinueTest(Game &$game, User &$user): Test|null { function addOrContinueTest(Game &$game, User &$user): Test|null
{
// check if the user had taken this test before // check if the user had taken this test before
$fetch_criteria = [["gameid", "=", (int)$game->getId()], "AND", ["nickname", "=", $user->getNickname()]]; $fetch_criteria = [["gameid", "=", (int)$game->getId()], "AND", ["nickname", "=", $user->getNickname()]];
$previous_tests = $this->db->findBy($fetch_criteria); $previous_tests = $this->db->findBy($fetch_criteria);
@ -384,12 +388,14 @@ class TestMgr
} }
// Delete test from the database. // Delete test from the database.
function deleteTest(string $testid): void { function deleteTest(string $testid): void
{
$this->db->deleteById($testid); $this->db->deleteById($testid);
} }
// Get concluded tests by game ID and nickname. // Get concluded tests by game ID and nickname.
function getConcludedTests(string $gameid, string $nickname): array { function getConcludedTests(string $gameid, string $nickname): array
{
$fetch_criteria = [["gameid", "=", (int)$gameid], "AND", ["nickname", "=", $nickname], "AND", ["state", "=", Test::TEST_CONCLUDED]]; $fetch_criteria = [["gameid", "=", (int)$gameid], "AND", ["nickname", "=", $nickname], "AND", ["state", "=", Test::TEST_CONCLUDED]];
$test_data_array = $this->db->findBy($fetch_criteria); $test_data_array = $this->db->findBy($fetch_criteria);
$tests = []; $tests = [];
@ -489,8 +495,10 @@ class TestMgr
$challenge_idx = $challenge_indices[$idhash]; $challenge_idx = $challenge_indices[$idhash];
// add up player answer // 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 if (trim($challenge["player_answer"]) !== "") {
$aggregated[$challenge_idx]["player_answers"][(int)$answer_idx]++; $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]++;
}
} }
} }
@ -514,7 +522,8 @@ class TestMgr
} }
// Upgrade test. Just load tests and save them. // Upgrade test. Just load tests and save them.
function upgradeTests(array $ids = []) : void { function upgradeTests(array $ids = []): void
{
$a = []; $a = [];
if ($ids === []) { if ($ids === []) {
$a = $this->db->findAll(); $a = $this->db->findAll();
@ -529,10 +538,11 @@ class TestMgr
} }
// Extract timed test IDs. Scan the database for tests with time limit ON. // Extract timed test IDs. Scan the database for tests with time limit ON.
function extractExpiredTimedTestIds(bool $ongoingOnly = true) : array { function extractExpiredTimedTestIds(bool $ongoingOnly = true): array
{
$query = [["time_limited", "=", true], "AND", ["end_limit_time", "<", time()]]; $query = [["time_limited", "=", true], "AND", ["end_limit_time", "<", time()]];
if ($ongoingOnly) { if ($ongoingOnly) {
$query = [ ...$query, "AND", ["state", "=", TEST_ONGOING]]; $query = [...$query, "AND", ["state", "=", TEST_ONGOING]];
} }
$qb = $this->db->createQueryBuilder(); $qb = $this->db->createQueryBuilder();

View File

@ -116,7 +116,8 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
</td> </td>
<td> <td>
<input type="button" value="Gyors áttekintés" 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}`)"> <input type="button" value="Részletes megjelenítés" onclick="window.open(`result_analyzer.php?game_id=${EDITED_GAME._id}`)"><br>
<input type="button" value="Csoportonkénti jelentés" onclick="show('report_generator')">
</td> </td>
</tr> </tr>
</table> </table>
@ -138,6 +139,19 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
</section> </section>
</section> </section>
<section class="window" shown="false" id="report_generator">
<section class="window-inner">
<input type="text" placeholder="Csoportok" value="" id="report_groups">
<input type="text" placeholder="Szűrő" value="" id="report_filter">
<select id="report_output_type">
<option value="pdf">PDF</option>
<option value="tex">TeX</option>
</select>
<input type="button" value="Előállítás" onclick="generate_game_report_by_groups()">
<input type="button" value="Bezárás" onclick="hide('report_generator')">
</section>
</section>
<script> <script>
list_all_games(); list_all_games();
</script> </script>

View File

@ -16,8 +16,10 @@ const MAIN_URL = "main.php";
const SESSION_NAME = "spreadquiz_sid"; const SESSION_NAME = "spreadquiz_sid";
const MAINTENANCE_FLAG_FILE = "MAINTENANCE"; const MAINTENANCE_FLAG_FILE = "MAINTENANCE";
const MISSING_IMAGE_PLACEHOLDER = "media/image-missing_120px.png"; const MISSING_IMAGE_PLACEHOLDER = "media/image-missing_120px.png";
const REPORT_DIR = DATADIR . DIRECTORY_SEPARATOR . "reports"; const REPORT_DIR = "report";
const REPORT_TEMPLATE_DIR = REPORT_DIR . DIRECTORY_SEPARATOR . "template";
const REPORT_PDF_OUTPUT_DIR = REPORT_DIR . DIRECTORY_SEPARATOR . "output";
const TEX_ENGINE = "xelatex";
session_name(SESSION_NAME); session_name(SESSION_NAME);
@ -34,6 +36,7 @@ function init_datadir() {
mkdir(DATADIR); mkdir(DATADIR);
mkdir(GAMEMEDIA_DIR); mkdir(GAMEMEDIA_DIR);
mkdir(REPORT_DIR); mkdir(REPORT_DIR);
mkdir(REPORT_PDF_OUTPUT_DIR);
} }
} }

View File

@ -533,16 +533,35 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
$gameid = trim($params["gameid"]); $gameid = trim($params["gameid"]);
$filter = trim($params["filter"] ?? ""); $filter = trim($params["filter"] ?? "");
$groups = explode_list(trim($params["groups"])); // TODO: lehessen több csoportra is $groups = explode_list(trim($params["groups"]));
$outtype = trim($params["outtype"] ?? "pdf"); $outtype = trim($params["outtype"] ?? "pdf");
// only PDF and TEX are valid
if (!in_array($outtype, ["pdf", "tex"])) {
return "FAIL";
}
$game = $gameMgr->getGame($gameid); $game = $gameMgr->getGame($gameid);
// verify game and access // verify game and access
if (($game === null) || (!$game->isUserContributorOrOwner($user->getNickname()) || !$user->hasQuizmasterPrivilege())) { if (($game === null) || ((!$game->isUserContributorOrOwner($user->getNickname()) && !$user->hasQuizmasterPrivilege()))) {
return "FAIL"; return "FAIL";
} }
// create destination directory and copy TeX frame file
$repId = uniqid($outtype);
$buildDir = REPORT_PDF_OUTPUT_DIR . DIRECTORY_SEPARATOR . $repId;
mkdir($buildDir);
$iter = new RecursiveDirectoryIterator(REPORT_TEMPLATE_DIR, FilesystemIterator::SKIP_DOTS);
foreach ($iter as $dir_item) {
if (!$iter->isDir()) {
$src = $dir_item->getPathname();
$dst = $buildDir . DIRECTORY_SEPARATOR . $dir_item->getFilename();
copy($src, $dst);
}
}
// assemble report // assemble report
$report = new Report($game->getName()); $report = new Report($game->getName());
foreach ($groups as $groupname) { foreach ($groups as $groupname) {
@ -552,28 +571,39 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
} }
// generate latex // generate latex
$report_dir = "report"; $report->saveTeX($buildDir);
$content_dir = $report_dir . DIRECTORY_SEPARATOR . "stats";
$report->saveTeX($content_dir);
$output = "";
$contentType = "";
if ($outtype === "pdf") { if ($outtype === "pdf") {
// run LuaLaTeX twice // run LuaLaTeX twice
chdir($report_dir); chdir($buildDir);
$tex_cmd = "lualatex -interaction=nonstopmode report.tex"; $tex_cmd = TEX_ENGINE . " -interaction=nonstopmode report.tex";
exec($tex_cmd); exec($tex_cmd);
exec($tex_cmd); exec($tex_cmd);
$output = "report.pdf"; // rename output
$origOutput = "report.pdf";
$output = $repId . ".pdf";
rename($origOutput, $output);
$contentType = "application/pdf"; $contentType = "application/pdf";
} else { } else if ($outtype === "tex") {
$output = $content_dir . DIRECTORY_SEPARATOR . "content.tex"; $output = $buildDir . DIRECTORY_SEPARATOR . $repId . ".zip";
$contentType = "text/plain"; $zip = new ZipArchive();
$zip->open($output, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$files = glob($buildDir . DIRECTORY_SEPARATOR . "*.tex");
foreach ($files as $file) {
$zip->addFile($file, basename($file));
}
$zip->addFromString("request.txt", "gameid: ${gameid}\ngroups: " . join(", ", $groups) . "\nfilter: ${filter}\nouttype: ${outtype}\n");
$zip->addFromString("request.url", $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"]);
$zip->close();
$contentType = "application/zip";
} }
// set content type
header("Content-Type: ${contentType}"); header("Content-Type: ${contentType}");
header("Content-Length: " . filesize($output)); header("Content-Length: " . filesize($output));
header("Content-Disposition: attachment; filename=" . basename($output));
// deploy the generated PDF // deploy the generated PDF
$f = fopen($output, "r"); $f = fopen($output, "r");

View File

@ -371,6 +371,17 @@ function list_results_by_game(game) {
}); });
} }
function generate_game_report_by_groups() {
let gameid = EDITED_GAME["_id"];
let filter = document.getElementById("report_filter").value.trim()
let groups = document.getElementById("report_groups").value.trim()
let outtype = document.getElementById("report_output_type").value;
let report_url = `interface.php?action=generate_report_by_groups&gameid=${gameid}&groups=${groups}&filter=${filter}&outtype=${outtype}`;
report_url = report_url.replace("#", "%23");
window.open(report_url, "_blank");
}
// function hint_all_groups(target_element_id) { // function hint_all_groups(target_element_id) {
// const hintbox_insert_fn = (record) => { // const hintbox_insert_fn = (record) => {
// let targetF = document.getElementById(target_element_id); // let targetF = document.getElementById(target_element_id);

View File

@ -140,11 +140,17 @@ function generate_report() {
let report_display = statsTab.document.getElementById("report_display"); let report_display = statsTab.document.getElementById("report_display");
report_display.innerHTML = ""; report_display.innerHTML = "";
let ch_n = 0;
stats.forEach((challenge) => { stats.forEach((challenge) => {
let challenge_box = document.createElement("section"); let challenge_box = document.createElement("section");
challenge_box.classList.add("challenge"); challenge_box.classList.add("challenge");
challenge_box.style.width = "100%"; challenge_box.style.width = "100%";
let seq_num = document.createElement("section");
seq_num.classList.add("seq-num");
seq_num.innerText = ++ch_n;
challenge_box.append(seq_num);
let img_url = challenge["image_url"]; let img_url = challenge["image_url"];
if (img_url !== "") { if (img_url !== "") {
let fig = document.createElement("img"); let fig = document.createElement("img");

View File

@ -1 +0,0 @@
Digit kvíz I-III eredmények kurzusonként

View File

@ -108,7 +108,7 @@
\begin{document} \begin{document}
\begin{center} \begin{center}
\Huge \IfFileExists{stats/title.tex}{\input{stats/title.tex}}{Kvíz eredmények} \Huge \IfFileExists{stats/title.tex}{\input{title.tex}}{Kvíz eredmények}
\end{center} \end{center}
\Large \Large
@ -116,6 +116,6 @@
\normalsize \normalsize
\clearpage \clearpage
\inputIfFileExists{stats/content.tex} \inputIfFileExists{content.tex}
\end{document} \end{document}