diff --git a/class/ReportBuilder.php b/class/ReportBuilder.php index 0038347..54fffec 100644 --- a/class/ReportBuilder.php +++ b/class/ReportBuilder.php @@ -26,7 +26,32 @@ class ReportBuilder } } -class Answer { +class TeXUtils +{ + static public function processCodeInserts(string $str) : string + { + $parts = explode("`", $str); + $output = ""; + for ($i = 0; $i < count($parts); $i++) { + $output .= $parts[$i]; + if ($i < count($parts) - 1) { + if (($i % 2) === 0) { + $output .= "\\texttt{"; + } else { + $output .= "}"; + } + } + } + return $output; + } + + static public function escape(string $str) : string { + return preg_replace("/([#%&])/", "\\\\$1", $str); + } +} + +class Answer +{ const CORRECT = "Correct"; const INCORRECT = "Incorrect"; @@ -34,19 +59,22 @@ class Answer { private string $type; // Answer's type private float $ratio; // Answer's ratio - public function __construct(string $type, string $text, float $ratio) { + public function __construct(string $type, string $text, float $ratio) + { $this->type = $type; $this->text = $text; $this->ratio = $ratio; } // Convert answer into TeX format. - public function genTeX(): string { - return "\\answer" . $this->type . "{" . $this->ratio . "}{" . $this->text . "}\n"; + public function genTeX(): string + { + return "\\answer" . $this->type . "{" . $this->ratio . "}{" . $this->text . "}\n"; } } -class Challenge { +class Challenge +{ private string $question; private array $answers; @@ -56,23 +84,26 @@ class Challenge { for ($i = 0; $i < count($data["answers"]); $i++) { $answer = $data["answers"][$i]; $ratio = $data["answer_ratio"][$i]; - $type = $answer === $data["correctAnswer"] ? Answer::CORRECT : Answer::INCORRECT; + $type = $answer === $data["correct_answer"] ? Answer::CORRECT : Answer::INCORRECT; $this->answers[] = new Answer($type, $answer, $ratio); } } - public function getQuestion(): string { + public function getQuestion(): string + { return $this->question; } - public function getAnswers(): array { + public function getAnswers(): array + { return $this->answers; } // Generate TeX representation. - public function genTeX(): string { + public function genTeX(): string + { $tex = "\\begin{question}{" . $this->question . "}\n"; - foreach ($this->answers as $answer) { + foreach ($this->answers as &$answer) { $tex .= $answer->genTeX(); } $tex .= "\\end{question}\n"; @@ -80,23 +111,30 @@ class Challenge { } } -class ReportSection { +class ReportSection +{ private string $title; private array $challenges; - function __construct(string $title, array $stats) { - $this->stats = $stats; + + function __construct(string $title, array $challenges) + { + $this->title = $title; + $this->challenges = array_map(fn($ch) => new Challenge($ch), $challenges); } - function getChallenges() : array { + function getChallenges(): array + { return $this->challenges; } - function getTitle() : string { + function getTitle(): string + { return $this->title; } // Generate TeX representation of this report. - function genTeX() : string { + function genTeX(): string + { $tex = "\\begin{quiz}{" . $this->title . "}\n"; foreach ($this->challenges as $challenge) { $tex .= $challenge->genTeX(); @@ -110,6 +148,7 @@ class Report { private string $title; private array $sections; + function __construct(string $title) { $this->title = $title; @@ -118,18 +157,20 @@ class Report // Add a new section to the report. - function addSection(ReportSection &$section) : void + function addSection(ReportSection &$section): void { $this->sections[] = $section; } // Generate TeX representation. - function genTeX() :string + function genTeX(): string { $tex = ""; foreach ($this->sections as $section) { $tex .= $section->genTeX() . "\n\n"; } + $tex = TeXUtils::processCodeInserts($tex); + $tex = TeXUtils::escape($tex); return $tex; } @@ -137,7 +178,8 @@ class Report const TITLE_FILE = "title.tex"; // Save TeX representation. - function saveTeX(string $dir) : void { + function saveTeX(string $dir): void + { file_put_contents($dir . DIRECTORY_SEPARATOR . self::CONTENT_FILE, $this->genTeX()); file_put_contents($dir . DIRECTORY_SEPARATOR . self::TITLE_FILE, $this->title); } diff --git a/interface.php b/interface.php index 2e495cd..64bc8b3 100644 --- a/interface.php +++ b/interface.php @@ -551,7 +551,26 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string } // generate latex - $report->saveTeX("report/"); + $report_dir = "report"; + $report->saveTeX($report_dir . DIRECTORY_SEPARATOR . "stats"); + + // run LuaLaTeX twice + chdir($report_dir); + $tex_cmd = "lualatex -interaction=nonstopmode report.tex"; + exec($tex_cmd); + exec($tex_cmd); + + $output = "report.pdf"; + + header("Content-Type: application/pdf"); + header("Content-Length: " . filesize($output)); + + // deploy the generated PDF + $pdf = fopen($output, "r"); + if ($pdf !== false) { + fpassthru($pdf); + fclose($pdf); + } return "OK"; } @@ -583,7 +602,7 @@ $rh->add("delete_games", ["ids"], PRIVILEGE_CREATOR, "delete_games", RESP_PLAIN, $rh->add("export_game_file_csv", ["gameid"], PRIVILEGE_CREATOR, "export_game_file_csv", RESP_NONE, "Export game CSV file."); $rh->add("get_results_by_gameid", ["gameid"], PRIVILEGE_CREATOR, "get_results_by_gameid", RESP_JSON, "Get game results."); $rh->add("generate_detailed_stats", ["gameid", "testids"], PRIVILEGE_CREATOR, "generate_detailed_game_stats", RESP_JSON, "Generate detailed game stats."); -$rh->add("generate_report_by_groups", ["gameid", "groups"], PRIVILEGE_CREATOR, "generate_report_by_groups", RESP_PLAIN, "Generate game reports for each specified group."); +$rh->add("generate_report_by_groups", ["gameid", "groups"], PRIVILEGE_CREATOR, "generate_report_by_groups", RESP_NONE, "Generate game reports for each specified group."); $rh->add("delete_tests", ["ids"], PRIVILEGE_CREATOR, "delete_tests", RESP_PLAIN, "Delete tests."); // execute processing if user is a creator