From 1fff851fb568594fbc30745fbca3cb677881c2e7 Mon Sep 17 00:00:00 2001 From: Epagris Date: Sat, 28 Sep 2024 13:23:08 +0200 Subject: [PATCH] - function doing report generation has been beautified - report generation windows was added to game manager - full TeX source downloading compressed into a ZIP --- class/TestMgr.php | 40 ++++++++++++------- game_manager_frame.php | 16 +++++++- globals.php | 7 +++- interface.php | 56 ++++++++++++++++++++------- js/gamemgr.js | 11 ++++++ js/result_analyzer.js | 6 +++ report/stats/title.tex | 1 - report/{ => template}/progressbar.sty | 0 report/{ => template}/report.tex | 4 +- 9 files changed, 107 insertions(+), 34 deletions(-) delete mode 100644 report/stats/title.tex rename report/{ => template}/progressbar.sty (100%) rename report/{ => template}/report.tex (92%) diff --git a/class/TestMgr.php b/class/TestMgr.php index e4729d1..46dce6e 100644 --- a/class/TestMgr.php +++ b/class/TestMgr.php @@ -18,7 +18,8 @@ class TestSummary private float $percentage; // Ratio of correct answers // Calculate percentage. - private function calculatePercentage() : void { + private function calculatePercentage(): void + { if ($this->challengeN > 0) { $this->percentage = $this->correctAnswerN / (double)$this->challengeN * 100.0; } else { // avoid division by zero @@ -288,12 +289,12 @@ class Test extends AutoStoring return $this->gameId; } - public function isConcluded() : bool + public function isConcluded(): bool { return $this->state === self::TEST_CONCLUDED; } - public function isOngoing() : bool + public function isOngoing(): bool { return $this->state === self::TEST_ONGOING; } @@ -332,25 +333,28 @@ class TestMgr } // Update test in the database. - function updateTest(Test &$test): void { + function updateTest(Test &$test): void + { $a = $test->toArray(); $this->db->update($a); } // Add test to the database. - function addTest(Game &$game, User &$user): Test { + function addTest(Game &$game, User &$user): Test + { // create new test - $test = new Test($this,$game, $user); + $test = new Test($this, $game, $user); // insert into database $a = $test->toArray(["_id"]); $a = $this->db->insert($a); - $test = new Test($this,$a); + $test = new Test($this, $a); 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 $fetch_criteria = [["gameid", "=", (int)$game->getId()], "AND", ["nickname", "=", $user->getNickname()]]; $previous_tests = $this->db->findBy($fetch_criteria); @@ -384,12 +388,14 @@ class TestMgr } // Delete test from the database. - function deleteTest(string $testid): void { + function deleteTest(string $testid): void + { $this->db->deleteById($testid); } // 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]]; $test_data_array = $this->db->findBy($fetch_criteria); $tests = []; @@ -489,8 +495,10 @@ class TestMgr $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]++; + if (trim($challenge["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]++; + } } } @@ -514,7 +522,8 @@ class TestMgr } // Upgrade test. Just load tests and save them. - function upgradeTests(array $ids = []) : void { + function upgradeTests(array $ids = []): void + { $a = []; if ($ids === []) { $a = $this->db->findAll(); @@ -529,10 +538,11 @@ class TestMgr } // 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()]]; if ($ongoingOnly) { - $query = [ ...$query, "AND", ["state", "=", TEST_ONGOING]]; + $query = [...$query, "AND", ["state", "=", TEST_ONGOING]]; } $qb = $this->db->createQueryBuilder(); diff --git a/game_manager_frame.php b/game_manager_frame.php index a74a687..1faeef3 100644 --- a/game_manager_frame.php +++ b/game_manager_frame.php @@ -116,7 +116,8 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) & - +
+ @@ -138,6 +139,19 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) & +
+
+ + + + + +
+
+ diff --git a/globals.php b/globals.php index 47893d3..ea0e4e9 100644 --- a/globals.php +++ b/globals.php @@ -16,8 +16,10 @@ const MAIN_URL = "main.php"; const SESSION_NAME = "spreadquiz_sid"; const MAINTENANCE_FLAG_FILE = "MAINTENANCE"; 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); @@ -34,6 +36,7 @@ function init_datadir() { mkdir(DATADIR); mkdir(GAMEMEDIA_DIR); mkdir(REPORT_DIR); + mkdir(REPORT_PDF_OUTPUT_DIR); } } diff --git a/interface.php b/interface.php index 79e3384..5d76a84 100644 --- a/interface.php +++ b/interface.php @@ -533,16 +533,35 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string $gameid = trim($params["gameid"]); $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"); + // only PDF and TEX are valid + if (!in_array($outtype, ["pdf", "tex"])) { + return "FAIL"; + } + $game = $gameMgr->getGame($gameid); // verify game and access - if (($game === null) || (!$game->isUserContributorOrOwner($user->getNickname()) || !$user->hasQuizmasterPrivilege())) { + if (($game === null) || ((!$game->isUserContributorOrOwner($user->getNickname()) && !$user->hasQuizmasterPrivilege()))) { 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 $report = new Report($game->getName()); foreach ($groups as $groupname) { @@ -552,28 +571,39 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string } // generate latex - $report_dir = "report"; - $content_dir = $report_dir . DIRECTORY_SEPARATOR . "stats"; - $report->saveTeX($content_dir); + $report->saveTeX($buildDir); - $output = ""; - $contentType = ""; if ($outtype === "pdf") { // run LuaLaTeX twice - chdir($report_dir); - $tex_cmd = "lualatex -interaction=nonstopmode report.tex"; + chdir($buildDir); + $tex_cmd = TEX_ENGINE . " -interaction=nonstopmode report.tex"; exec($tex_cmd); exec($tex_cmd); - $output = "report.pdf"; + // rename output + $origOutput = "report.pdf"; + $output = $repId . ".pdf"; + rename($origOutput, $output); + $contentType = "application/pdf"; - } else { - $output = $content_dir . DIRECTORY_SEPARATOR . "content.tex"; - $contentType = "text/plain"; + } else if ($outtype === "tex") { + $output = $buildDir . DIRECTORY_SEPARATOR . $repId . ".zip"; + $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-Length: " . filesize($output)); + header("Content-Disposition: attachment; filename=" . basename($output)); // deploy the generated PDF $f = fopen($output, "r"); diff --git a/js/gamemgr.js b/js/gamemgr.js index 7ddf486..1404720 100644 --- a/js/gamemgr.js +++ b/js/gamemgr.js @@ -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) { // const hintbox_insert_fn = (record) => { // let targetF = document.getElementById(target_element_id); diff --git a/js/result_analyzer.js b/js/result_analyzer.js index 513b7ff..58ac09b 100644 --- a/js/result_analyzer.js +++ b/js/result_analyzer.js @@ -140,11 +140,17 @@ function generate_report() { let report_display = statsTab.document.getElementById("report_display"); report_display.innerHTML = ""; + let ch_n = 0; stats.forEach((challenge) => { let challenge_box = document.createElement("section"); challenge_box.classList.add("challenge"); 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"]; if (img_url !== "") { let fig = document.createElement("img"); diff --git a/report/stats/title.tex b/report/stats/title.tex deleted file mode 100644 index 147f3d5..0000000 --- a/report/stats/title.tex +++ /dev/null @@ -1 +0,0 @@ -Digit kvíz I-III eredmények kurzusonként \ No newline at end of file diff --git a/report/progressbar.sty b/report/template/progressbar.sty similarity index 100% rename from report/progressbar.sty rename to report/template/progressbar.sty diff --git a/report/report.tex b/report/template/report.tex similarity index 92% rename from report/report.tex rename to report/template/report.tex index 52d7721..bef3bad 100644 --- a/report/report.tex +++ b/report/template/report.tex @@ -108,7 +108,7 @@ \begin{document} \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} \Large @@ -116,6 +116,6 @@ \normalsize \clearpage -\inputIfFileExists{stats/content.tex} +\inputIfFileExists{content.tex} \end{document}