diff --git a/api/code.txt b/api/code.txt index 5e572945..64f72f7d 100644 --- a/api/code.txt +++ b/api/code.txt @@ -25,6 +25,7 @@ 22 file submit disabled 23 view log disabled 24 register disabled +25 problem disabled // 3x: permission 31 access denied @@ -37,7 +38,7 @@ 44 file not found 45 file exist -// 10x: info +// 10x: info IGNORED 101 not in contest mode 102 modified nothing 103 contest not started diff --git a/api/contest/problems/add.php b/api/contest/problems/add.php index 99b68416..6d701cff 100644 --- a/api/contest/problems/add.php +++ b/api/contest/problems/add.php @@ -36,6 +36,7 @@ $attachment = isset($_FILES["attm"]) ? $_FILES["attm"] : null; $description = reqForm("desc"); $test = isset($_POST["test"]) ? json_decode($_POST["test"], true) : Array(); + $disabled = withType(getForm("disabled"), "boolean", false); $code = problemAdd($id, Array( "name" => $name, @@ -49,6 +50,7 @@ "accept" => $accept, "description" => $description, "test" => $test, + "disabled" => $disabled ), $image, $attachment); switch ($code) { diff --git a/api/contest/problems/attachment.php b/api/contest/problems/attachment.php index a48538a0..ef8b8833 100644 --- a/api/contest/problems/attachment.php +++ b/api/contest/problems/attachment.php @@ -25,6 +25,12 @@ require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; + if (!problemExist($id)) + stop(45, "Không tìm thấy đề của id đã cho!", 404, Array( "id" => $id )); + + if (problemDisabled($id) && $_SESSION["id"] !== "admin") + stop(25, "Đề $id đã bị tắt", 403, Array( "id" => $id )); + if (problemGetAttachment($id, !getQuery("embed", false)) === PROBLEM_OKAY) writeLog("INFO", "Đã tải tệp đính kèm của bài \"". $_GET["id"] ."\""); else diff --git a/api/contest/problems/edit.php b/api/contest/problems/edit.php index 5d1875f7..1eb9cfde 100644 --- a/api/contest/problems/edit.php +++ b/api/contest/problems/edit.php @@ -24,19 +24,21 @@ require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; $id = preg_replace("/[.\/\\\\]/m", "", reqForm("id")); + $problem = problemGet($id, true); $name = getForm("name"); $description = getForm("desc"); - $point = withType(reqForm("point"), "integer"); + $point = withType(getForm("point"), "integer"); $time = withType(getForm("time"), "integer"); $memLimit = withType(getForm("memory"), "integer"); - $inpType = getForm("inpType"); - $outType = getForm("outType"); - $accept = json_decode(getForm("acpt", Array()), true); - $test = json_decode(getForm("test", Array()), true); + $inpType = getForm("inpType", $problem["type"]["inp"]); + $outType = getForm("outType", $problem["type"]["out"]); + $accept = json_decode(getForm("acpt", "[]"), true) ?: null; + $test = json_decode(getForm("test", "[]"), true) ?: null; $image = isset($_FILES["img"]) ? $_FILES["img"] : null; $attachment = isset($_FILES["attm"]) ? $_FILES["attm"] : null; + $disabled = withType(getForm("disabled"), "boolean"); $code = problemEdit($id, Array( "name" => $name, @@ -50,6 +52,7 @@ ), "accept" => $accept, "test" => $test, + "disabled" => $disabled ), $image, $attachment); switch ($code) { diff --git a/api/contest/problems/get.php b/api/contest/problems/get.php index dfe3c52e..923258df 100644 --- a/api/contest/problems/get.php +++ b/api/contest/problems/get.php @@ -18,7 +18,17 @@ contest_timeRequire([CONTEST_STARTED], false); require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; - $data = problemGet($id); + $data = problemGet($id, $_SESSION["id"] === "admin"); + + switch ($data) { + case PROBLEM_ERROR_IDREJECT: + stop(44, "Không tìm thấy để của id đã cho!", 404, Array( "id" => $id )); + break; + + case PROBLEM_ERROR_DISABLED: + stop(25, "Đề $id đã bị tắt", 403, Array( "id" => $id )); + break; + } if (isset($data["image"])) $data["image"] = "/api/contest/problems/image?id=". $id; @@ -42,7 +52,4 @@ "embed" => false ); - if ($data === PROBLEM_ERROR_IDREJECT) - stop(44, "Không tìm thấy để của id đã cho!", 404, Array( "id" => $id )); - else - stop(0, "Success!", 200, $data); \ No newline at end of file + stop(0, "Success!", 200, $data); \ No newline at end of file diff --git a/api/contest/problems/image.php b/api/contest/problems/image.php index b493ab84..2daa5659 100644 --- a/api/contest/problems/image.php +++ b/api/contest/problems/image.php @@ -32,7 +32,7 @@ function showImage(string $path) { require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; - if (!problemExist($id)) + if (!problemExist($id) || (problemDisabled($id) && $_SESSION["id"] !== "admin")) showImage(PROBLEM_DIR ."/image.default"); if (isset($problemList[$id]["image"])) { diff --git a/api/contest/problems/list.php b/api/contest/problems/list.php index 4c780c0b..ca096afc 100644 --- a/api/contest/problems/list.php +++ b/api/contest/problems/list.php @@ -16,4 +16,4 @@ contest_timeRequire([CONTEST_STARTED], false); require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; - stop(0, "Thành công!", 200, problemList()); \ No newline at end of file + stop(0, "Thành công!", 200, problemList($_SESSION["id"] === "admin")); \ No newline at end of file diff --git a/api/contest/upload.php b/api/contest/upload.php index 9a49d881..7035966b 100644 --- a/api/contest/upload.php +++ b/api/contest/upload.php @@ -40,6 +40,9 @@ if (!problemExist($filename)) stop(44, "Không có đề cho bài này!", 404, Array( "file" => $filename )); + if (problemDisabled($filename) && $_SESSION["id"] !== "admin") + stop(25, "Đề $filename đã bị tắt", 403, Array( "id" => $filename )); + if (!problemCheckExtension($filename, $extension)) stop(43, "Không chấp nhận tệp!", 415); } else diff --git a/assets/css/core.css b/assets/css/core.css index fa7f810b..3e35f7ea 100644 --- a/assets/css/core.css +++ b/assets/css/core.css @@ -1222,6 +1222,10 @@ body.guest #problemp.hide { animation: problemsListShow 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s 1 normal forwards; } +#problemp .problemsContainer .problemsList:empty { + height: 100%; +} + #problemp .problemsContainer .problemsList .item { position: relative; display: flex; @@ -1289,6 +1293,10 @@ body.guest #problemp.hide { border-radius: 4px; } +#problemp .problemsContainer .problemsList .item[disabled="true"] { + background-color: rgba(243, 184, 205, 0.6); +} + #problemp .problemsContainer .problem { position: absolute; width: 100%; @@ -1530,8 +1538,9 @@ body.guest #problemp.hide { #problemp .problemsContainer .problem .attachment .embed { width: 100%; height: 0; + min-height: 80px; max-height: calc(100vh - 160px); - padding: 10px 0 20px 0; + margin: 10px 0 20px 0; transition: height 0.4s ease-in; } diff --git a/assets/css/dark.css b/assets/css/dark.css index 3db625bb..4a9ed0f8 100644 --- a/assets/css/dark.css +++ b/assets/css/dark.css @@ -98,6 +98,10 @@ body.dark #problemp .problemsContainer .problemsList .item { border-bottom-color: rgb(88, 88, 88); } +body.dark #problemp .problemsContainer .problemsList .item[disabled="true"] { + background-color: rgba(117, 57, 78, 0.6); +} + body.dark #problemp .problemsContainer .problemsList .item:hover { background-color: rgb(70, 70, 70); } @@ -343,15 +347,15 @@ body.dark .problemSettings .header div span::before { color: rgba(190, 190, 190, 0.54); } -body.dark .problemSettings .problemsContainer.problemsList li.item:hover { +body.dark .problemSettings .problemsContainer .problemsList li.item:hover { background-color: rgba(36, 36, 36, 0.4); } -body.dark .problemSettings .problemsContainer.problemsList li.item .title .id { +body.dark .problemSettings .problemsContainer .problemsList li.item .title .id { color: rgb(160, 160, 160); } -body.dark .problemSettings .problemsContainer.problemsList li.item .title .name { +body.dark .problemSettings .problemsContainer .problemsList li.item .title .name { color: rgb(214, 214, 214); } diff --git a/assets/css/default.css b/assets/css/default.css index 6474594a..86c8f917 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -505,7 +505,7 @@ body.dark separator { .popupContainer .popupWindow .body .customNode { position: relative; flex-shrink: 1; - margin: 10px 0; + margin: 10px 0; width: 100%; } diff --git a/assets/css/switch.css b/assets/css/switch.css index a3267da7..04d5d07d 100644 --- a/assets/css/switch.css +++ b/assets/css/switch.css @@ -7,7 +7,7 @@ ? |-----------------------------------------------------------------------------------------------| */ -.ios-switch { +.iosSwitch { display: inline-block; position: relative; cursor: pointer; @@ -19,7 +19,7 @@ user-select: none; } -.ios-switch .background { +.iosSwitch .background { position: absolute; top: 0; left: 0; @@ -33,13 +33,13 @@ } /* Hide the browser's default checkbox */ -.ios-switch input { +.iosSwitch input { position: absolute; opacity: 0; cursor: pointer; } -.ios-switch .circle { +.iosSwitch .circle { position: absolute; display: inline-block; top: 0; @@ -55,12 +55,12 @@ animation-fill-mode: forwards; } -.ios-switch input:checked + .background { +.iosSwitch input:checked + .background { background: #4cd864; border-color: #4cd864; } -.ios-switch input:checked ~ .circle { +.iosSwitch input:checked ~ .circle { animation: swanmfw 0.3s cubic-bezier(0.48,-0.07, 0.49, 1.08); animation-fill-mode: forwards; } @@ -94,7 +94,7 @@ } } -.material-switch { +.materialSwitch { display: inline-block; position: relative; cursor: pointer; @@ -106,13 +106,13 @@ user-select: none; } -.material-switch input { +.materialSwitch input { position: absolute; opacity: 0; cursor: pointer; } -.material-switch .track { +.materialSwitch .track { position: absolute; width: 40px; height: 16px; @@ -121,7 +121,7 @@ transition: color 0.2s ease-in-out; } -.material-switch .track::after { +.materialSwitch .track::after { content: ""; position: absolute; background-color: #ffffff; @@ -135,15 +135,32 @@ transition: all 0.2s ease-in-out; } -.material-switch input:checked + .track { +.materialSwitch input:checked + .track { background-color: rgba(26, 115, 232, 0.5); } -.material-switch input:checked + .track::after { +.materialSwitch input:checked + .track::after { left: 16px; background-color: #4285f4; } +.materialSwitch input:disabled + .track { + cursor: not-allowed; + background-color: rgb(146, 146, 146); +} + +.materialSwitch input:disabled + .track::after { + background-color: rgb(116, 116, 116); +} + +.materialSwitch input:checked:disabled + .track { + background-color: rgba(7, 68, 148, 0.5); +} + +.materialSwitch input:checked:disabled + .track::after { + background-color: #1e53a8; +} + .sq-checkbox { position: relative; display: block; diff --git a/assets/js/core.js b/assets/js/core.js index 9e7aaf56..5af98379 100644 --- a/assets/js/core.js +++ b/assets/js/core.js @@ -900,7 +900,7 @@ const core = { let html = ""; data.forEach(item => { html += ` - +
@@ -2062,23 +2062,28 @@ const core = { }); let data = response.data; - this.list.innerHTML = ""; - data.forEach(item => { - html = [ - `
  • `, - ``, - `
      `, - `
    • ${item.id}
    • `, - `
    • ${item.name}
    • `, - `
    `, - `
    `, - ``, - ``, - `
    `, - `
  • `, - ].join("\n"); - this.list.innerHTML += html; - }) + let html = ""; + emptyNode(this.list); + for (let item of data) + html += ` +
  • + +
      +
    • ${item.id}
    • +
    • ${item.name}
    • +
    +
    + + +
    +
    + + +
    +
  • + ` + + this.list.innerHTML = html; }, resetForm() { @@ -2344,6 +2349,23 @@ const core = { }) return true; + }, + + async toggleDisabled(id, targetSwitch) { + targetSwitch.disabled = true; + targetSwitch.checked = !targetSwitch.checked; + + await myajax({ + url: "/api/contest/problems/edit", + method: "POST", + form: { + id: id, + disabled: !targetSwitch.checked, + token: API_TOKEN + } + }) + + targetSwitch.disabled = false; } } }, diff --git a/data/images/.dummy b/data/images/.dummy new file mode 100644 index 00000000..b83ac69c --- /dev/null +++ b/data/images/.dummy @@ -0,0 +1 @@ +Just to let this folder exist \ No newline at end of file diff --git a/data/images/icon.jpg b/data/images/icon.jpg deleted file mode 100644 index a8699b14..00000000 Binary files a/data/images/icon.jpg and /dev/null differ diff --git a/data/problems/bai1/data.json b/data/problems/bai1/data.json index 68f94fc3..cfb7b067 100644 --- a/data/problems/bai1/data.json +++ b/data/problems/bai1/data.json @@ -1,6 +1,6 @@ { "name": "\u01af\u1edbc S\u1ed1", - "point": 112, + "point": 10, "time": 1, "memory": 1024, "type": { @@ -22,5 +22,6 @@ "out": "1 5 107 535" } ], - "attachment": "hello world.pdf" + "attachment": "hello world.pdf", + "disabled": true } \ No newline at end of file diff --git a/data/problems/bai2/data.json b/data/problems/bai2/data.json index 78d186dd..5d2e81e4 100644 --- a/data/problems/bai2/data.json +++ b/data/problems/bai2/data.json @@ -21,5 +21,6 @@ "inp": "8", "out": "0" } - ] + ], + "disabled": false } \ No newline at end of file diff --git a/data/problems/bai3/data.json b/data/problems/bai3/data.json index ac152168..86ee1bcd 100644 --- a/data/problems/bai3/data.json +++ b/data/problems/bai3/data.json @@ -26,5 +26,6 @@ "out": "1" } ], - "image": "prime number chart ii.jpg" + "image": "prime number chart ii.jpg", + "disabled": false } \ No newline at end of file diff --git a/data/problems/bai4/data.json b/data/problems/bai4/data.json index d0497faa..f314b9ee 100644 --- a/data/problems/bai4/data.json +++ b/data/problems/bai4/data.json @@ -18,5 +18,6 @@ "out": "2, 3, 5, 7, 11, 13, 17, 19, 23" } ], - "image": "prime number chart ii.jpg" + "image": "prime number chart ii.jpg", + "disabled": false } \ No newline at end of file diff --git a/data/problems/problem.php b/data/problems/problem.php index 26d5fa16..29279719 100644 --- a/data/problems/problem.php +++ b/data/problems/problem.php @@ -22,18 +22,21 @@ define("PROBLEM_ERROR_FILETOOLARGE", 3); define("PROBLEM_ERROR_FILEREJECT", 4); define("PROBLEM_ERROR_FILENOTFOUND", 5); + define("PROBLEM_ERROR_DISABLED", 6); - function problemList() { + function problemList(Bool $showDisabled = false) { global $problemList; $list = Array(); foreach($problemList as $i => $item) { - array_push($list, Array( - "id" => $i, - "name" => $item["name"], - "point" => $item["point"], - "image" => "/api/contest/problems/image?id=". $i - )); + if ($showDisabled || !$item["disabled"]) + array_push($list, Array( + "id" => $i, + "name" => $item["name"], + "point" => $item["point"], + "image" => "/api/contest/problems/image?id=". $i, + "disabled" => $item["disabled"] + )); } return $list; @@ -60,14 +63,18 @@ function problemListAttachment() { return $list; } - function problemGet(String $id) { + function problemGet(String $id, Bool $bypassDisabled = false) { global $problemList; + if (!problemExist($id)) return PROBLEM_ERROR_IDREJECT; $data = $problemList[$id]; - $data["id"] = $id; + if ($data["disabled"] && !$bypassDisabled) + return PROBLEM_ERROR_DISABLED; + + $data["id"] = $id; return $data; } @@ -275,6 +282,11 @@ function problemExist(String $id) { return isset($problemList[$id]); } + function problemDisabled(String $id) { + global $problemList; + return (isset($problemList[$id]["disabled"]) && $problemList[$id]["disabled"]); + } + function problemCheckExtension(String $id, String $ext) { global $problemList; if (!problemExist($id)) diff --git a/data/problems/soluong/data.json b/data/problems/soluong/data.json index 4d594a8a..1ce60e70 100644 --- a/data/problems/soluong/data.json +++ b/data/problems/soluong/data.json @@ -4,8 +4,8 @@ "time": 1, "memory": 1024, "type": { - "inp": "SOLUONG.INP", - "out": "SOLUONG.OUT" + "inp": null, + "out": null }, "accept": [ "pas", @@ -23,5 +23,6 @@ "inp": "HOALAN", "out": "2" } - ] + ], + "disabled": false } \ No newline at end of file diff --git a/lib/belibrary.php b/lib/belibrary.php index 07cd7ce7..458285f0 100644 --- a/lib/belibrary.php +++ b/lib/belibrary.php @@ -185,6 +185,11 @@ function getKey(Array $data, String $key, $isNull = null) { * + "unknown type" */ function withType($data, $type = "string", $isNot = null) { + if ($type === "boolean") + return ($data === "true" || $data === "false" || $data === "0" || $data === "1") + ? ($data === "true" || $data === "0") + : $isNot; + if (isset($data) && !settype($data, $type)) return $isNot; diff --git a/lib/logParser.php b/lib/logParser.php index 778f3a67..ba3e5ae2 100644 --- a/lib/logParser.php +++ b/lib/logParser.php @@ -1,261 +1,261 @@ logPath = $logPath; - $this -> mode = $mode; - $this -> logIsFailed = false; - $this -> passed = 0; - $this -> failed = 0; - $this -> blankLinePos = 3; - } - - public function parse() { - $file = file($this -> logPath, FILE_IGNORE_NEW_LINES); - $header = $this -> __parseHeader($file); - - if ($this -> mode === LOGPARSER_MODE_FULL) { - $testResult = $this -> __parseTestResult($file); - $header["testPassed"] = $this -> passed; - $header["testFailed"] = $this -> failed; - } else - $testResult = Array(); - - return Array( - "header" => $header, - "test" => $testResult - ); - } - - private function __parseHeader($file) { - $data = Array( - "status" => null, - "user" => null, - "name" => null, - "problem" => null, - "problemName" => null, - "problemPoint" => null, - "point" => 0, - "testPassed" => 0, - "testFailed" => 0, - "description" => Array(), - "error" => Array(), - "file" => Array( - "base" => null, - "name" => null, - "extension" => null, - "logFilename" => pathinfo($this -> logPath, PATHINFO_FILENAME), - "lastModify" => filemtime($this -> logPath) - ) - ); - - $firstLine = $file[0]; - $l1matches = []; - - //? check with error regex template - if (preg_match_all("/(.+)‣(.+): ℱ (.+\w)/m", $firstLine, $l1matches, PREG_SET_ORDER, 0)) { - //* test match failed template - $data["status"] = "failed"; - $data["point"] = 0; - $data["description"] = [$l1matches[0][3]]; - $this -> logIsFailed = true; - - //? error detail start from line 3 - for ($i = 2; $i < count($file); $i++) - array_push($data["error"], $file[$i]); - - } else if (preg_match_all("/(.+)‣(.+): Chưa chấm/m", $firstLine, $l1matches, PREG_SET_ORDER, 0)) { - //* test match skipped template - $data["status"] = "skipped"; - $data["point"] = 0; - $data["description"] = ["Chưa chấm"]; - $this -> logIsFailed = true; - } else { - //* test match pass template - preg_match_all("/(.+)‣(.+): (.+\w)/m", $firstLine, $l1matches, PREG_SET_ORDER, 0); - $data["point"] = $this -> __f($l1matches[0][3]); - $data["status"] = ($data["point"] == 0) ? "accepted" : "passed"; - - for ($i = 2; $i < count($file); $i++) { - //? Break on blank line - if (empty($file[$i])) { - $this -> blankLinePos = $i; - break; - } - - array_push($data["description"], $file[$i]); - } - } - - //! this is weird. soo weird - $data["user"] = trim($l1matches[0][1], ""); - $data["problem"] = $l1matches[0][2]; - $problemData = problemGet($data["problem"]); - - if ($problemData !== PROBLEM_ERROR_IDREJECT) { - $data["problemName"] = $problemData["name"]; - $data["problemPoint"] = $this -> __f($problemData["point"]); - - if ($data["problemPoint"] <= $data["point"]) - $data["status"] = "correct"; - } - - if (isset($file[1])) { - $problemFileInfo = pathinfo($file[1]); - $data["file"]["base"] = $problemFileInfo["filename"]; - $data["file"]["name"] = $problemFileInfo["basename"]; - $data["file"]["extension"] = $problemFileInfo["extension"]; - } else { - $problemFileInfo = parseLogName($this -> logPath); - $data["file"]["base"] = $problemFileInfo["problem"]; - $data["file"]["name"] = $problemFileInfo["name"]; - $data["file"]["extension"] = $problemFileInfo["extension"]; - } - - return $data; - } - - private function __parseTestResult($file) { - if ($this -> logIsFailed === true) - return Array(); - - $this -> passed = 0; - $this -> failed = 0; - $data = Array(); - $lineData = null; - $lineInitTemplate = Array( - "status" => "passed", - "test" => null, - "point" => 0, - "runtime" => 0, - "detail" => Array(), - "other" => Array( - "output" => null, - "answer" => null, - "error" => null, - ) - ); - - # test result start after blank line - for ($i = $this -> blankLinePos; $i < count($file); $i++) { - $line = $file[$i]; - if (empty($line)) - continue; - - $lineParsed = []; - if (preg_match_all("/.+‣.+‣(.+): (.+|\d+)/m", $line, $lineParsed, PREG_SET_ORDER, 0)) { - # line match begin of test data format - if (!empty($lineData)) - array_push($data, $lineData); - - $lineData = $lineInitTemplate; - $lineData["test"] = $lineParsed[0][1]; - $lineData["point"] = $this -> __f($lineParsed[0][2]); - - if ($lineData["point"] == 0) { - $lineData["status"] = "accepted"; - $this -> failed++; - } else { - $lineData["status"] = "passed"; - $this -> passed++; - } - - } else if (preg_match_all("/.+ ≈ (.+) .+/m", $line, $lineParsed, PREG_SET_ORDER, 0)) - # line match runtime format - $lineData["runtime"] = $this -> __f($lineParsed[0][1]); - - else if (preg_match_all("/.*Output.*: ((.+)(?=\.)|(.+))/m", $line, $lineParsed, PREG_SET_ORDER, 0)) - # line match output data format - $lineData["other"]["output"] = $lineParsed[0][1]; - - else if (preg_match_all("/.*Answer.*: ((.+)(?=\.)|(.+))/m", $line, $lineParsed, PREG_SET_ORDER, 0)) - # line match answer data format - $lineData["other"]["answer"] = $lineParsed[0][1]; - - else if (preg_match_all("/(Command: .+)/m", $line, $lineParsed, PREG_SET_ORDER, 0)) { - # line match error detail format - $lineData["other"]["error"] = $lineParsed[0][1]; - $lineData["status"] = "failed"; - } - - else - # else is detail, cuz detail have no specific format - array_push($lineData["detail"], $line); - } - - if (!empty($lineData)) - array_push($data, $lineData); - - return $data; - } - - private function __f($str) { - return round((float) str_replace(",", ".", $str), 2); - } - } - - function parseLogName(String $path) { - $name = basename($path); - - $parse = []; - if (preg_match_all("/(.+)\[(.+)\]\[(.+)\]\.([^\.]+)\.?(log)?/m", $name, $parse, PREG_SET_ORDER, 0)) { - $problemData = problemGet($parse[0][3]); - $problemName = null; - $problemPoint = null; - - if ($problemData !== PROBLEM_ERROR_IDREJECT) { - $problemName = $problemData["name"]; - $problemPoint = $problemData["point"]; - } - - return Array( - "id" => $parse[0][1], - "user" => $parse[0][2], - "problem" => $parse[0][3], - "problemName" => $problemName, - "problemPoint" => $problemPoint, - "extension" => $parse[0][4], - "name" => $parse[0][3] .".". $parse[0][4], - "isLogFile" => isset($parse[0][5]) - ); - } - - return false; - } \ No newline at end of file + //? |-----------------------------------------------------------------------------------------------| + //? | /lib/logParser.php | + //? | | + //? | Copyright (c) 2018-2020 Belikhun. All right reserved | + //? | Licensed under the MIT License. See LICENSE in the project root for license information. | + //? |-----------------------------------------------------------------------------------------------| + /** + * A module to parse Themis generated log files + * + * @package logParser + */ + + require_once $_SERVER["DOCUMENT_ROOT"] ."/lib/belibrary.php"; + require_once $_SERVER["DOCUMENT_ROOT"] ."/data/problems/problem.php"; + + /** + * Parse all the log file + */ + define("LOGPARSER_MODE_FULL", "f"); + + /** + * Parse the log filename and header only + */ + define("LOGPARSER_MODE_MINIMAL", "m"); + + class logParser { + + /** + * + * Parse the log file generated by Themis + * + * @param logPath Path to log file + * @param mode Specify parser mode + * @return Array Contains parsed data + * + */ + public function __construct(String $logPath, String $mode = LOGPARSER_MODE_MINIMAL) { + if (!file_exists($logPath)) + throw new Error("File not exist", 44); + + $this -> logPath = $logPath; + $this -> mode = $mode; + $this -> logIsFailed = false; + $this -> passed = 0; + $this -> failed = 0; + $this -> blankLinePos = 3; + } + + public function parse() { + $file = file($this -> logPath, FILE_IGNORE_NEW_LINES); + $header = $this -> __parseHeader($file); + + if ($this -> mode === LOGPARSER_MODE_FULL) { + $testResult = $this -> __parseTestResult($file); + $header["testPassed"] = $this -> passed; + $header["testFailed"] = $this -> failed; + } else + $testResult = Array(); + + return Array( + "header" => $header, + "test" => $testResult + ); + } + + private function __parseHeader($file) { + $data = Array( + "status" => null, + "user" => null, + "name" => null, + "problem" => null, + "problemName" => null, + "problemPoint" => null, + "point" => 0, + "testPassed" => 0, + "testFailed" => 0, + "description" => Array(), + "error" => Array(), + "file" => Array( + "base" => null, + "name" => null, + "extension" => null, + "logFilename" => pathinfo($this -> logPath, PATHINFO_FILENAME), + "lastModify" => filemtime($this -> logPath) + ) + ); + + $firstLine = $file[0]; + $l1matches = []; + + //? check with error regex template + if (preg_match_all("/(.+)‣(.+): ℱ (.+\w)/m", $firstLine, $l1matches, PREG_SET_ORDER, 0)) { + //* test match failed template + $data["status"] = "failed"; + $data["point"] = 0; + $data["description"] = [$l1matches[0][3]]; + $this -> logIsFailed = true; + + //? error detail start from line 3 + for ($i = 2; $i < count($file); $i++) + array_push($data["error"], $file[$i]); + + } else if (preg_match_all("/(.+)‣(.+): Chưa chấm/m", $firstLine, $l1matches, PREG_SET_ORDER, 0)) { + //* test match skipped template + $data["status"] = "skipped"; + $data["point"] = 0; + $data["description"] = ["Chưa chấm"]; + $this -> logIsFailed = true; + } else { + //* test match pass template + preg_match_all("/(.+)‣(.+): (.+\w)/m", $firstLine, $l1matches, PREG_SET_ORDER, 0); + $data["point"] = $this -> __f($l1matches[0][3]); + $data["status"] = ($data["point"] == 0) ? "accepted" : "passed"; + + for ($i = 2; $i < count($file); $i++) { + //? Break on blank line + if (empty($file[$i])) { + $this -> blankLinePos = $i; + break; + } + + array_push($data["description"], $file[$i]); + } + } + + //! this is weird. soo weird + $data["user"] = trim($l1matches[0][1], ""); + $data["problem"] = $l1matches[0][2]; + $problemData = problemGet($data["problem"], $_SESSION["id"] === "admin"); + + if ($problemData !== PROBLEM_ERROR_IDREJECT && $problemData !== PROBLEM_ERROR_DISABLED) { + $data["problemName"] = $problemData["name"]; + $data["problemPoint"] = $this -> __f($problemData["point"]); + + if ($data["problemPoint"] <= $data["point"]) + $data["status"] = "correct"; + } + + if (isset($file[1])) { + $problemFileInfo = pathinfo($file[1]); + $data["file"]["base"] = $problemFileInfo["filename"]; + $data["file"]["name"] = $problemFileInfo["basename"]; + $data["file"]["extension"] = $problemFileInfo["extension"]; + } else { + $problemFileInfo = parseLogName($this -> logPath); + $data["file"]["base"] = $problemFileInfo["problem"]; + $data["file"]["name"] = $problemFileInfo["name"]; + $data["file"]["extension"] = $problemFileInfo["extension"]; + } + + return $data; + } + + private function __parseTestResult($file) { + if ($this -> logIsFailed === true) + return Array(); + + $this -> passed = 0; + $this -> failed = 0; + $data = Array(); + $lineData = null; + $lineInitTemplate = Array( + "status" => "passed", + "test" => null, + "point" => 0, + "runtime" => 0, + "detail" => Array(), + "other" => Array( + "output" => null, + "answer" => null, + "error" => null, + ) + ); + + # test result start after blank line + for ($i = $this -> blankLinePos; $i < count($file); $i++) { + $line = $file[$i]; + if (empty($line)) + continue; + + $lineParsed = []; + if (preg_match_all("/.+‣.+‣(.+): (.+|\d+)/m", $line, $lineParsed, PREG_SET_ORDER, 0)) { + # line match begin of test data format + if (!empty($lineData)) + array_push($data, $lineData); + + $lineData = $lineInitTemplate; + $lineData["test"] = $lineParsed[0][1]; + $lineData["point"] = $this -> __f($lineParsed[0][2]); + + if ($lineData["point"] == 0) { + $lineData["status"] = "accepted"; + $this -> failed++; + } else { + $lineData["status"] = "passed"; + $this -> passed++; + } + + } else if (preg_match_all("/.+ ≈ (.+) .+/m", $line, $lineParsed, PREG_SET_ORDER, 0)) + # line match runtime format + $lineData["runtime"] = $this -> __f($lineParsed[0][1]); + + else if (preg_match_all("/.*Output.*: ((.+)(?=\.)|(.+))/m", $line, $lineParsed, PREG_SET_ORDER, 0)) + # line match output data format + $lineData["other"]["output"] = $lineParsed[0][1]; + + else if (preg_match_all("/.*Answer.*: ((.+)(?=\.)|(.+))/m", $line, $lineParsed, PREG_SET_ORDER, 0)) + # line match answer data format + $lineData["other"]["answer"] = $lineParsed[0][1]; + + else if (preg_match_all("/(Command: .+)/m", $line, $lineParsed, PREG_SET_ORDER, 0)) { + # line match error detail format + $lineData["other"]["error"] = $lineParsed[0][1]; + $lineData["status"] = "failed"; + } + + else + # else is detail, cuz detail have no specific format + array_push($lineData["detail"], $line); + } + + if (!empty($lineData)) + array_push($data, $lineData); + + return $data; + } + + private function __f($str) { + return round((float) str_replace(",", ".", $str), 2); + } + } + + function parseLogName(String $path) { + $name = basename($path); + + $parse = []; + if (preg_match_all("/(.+)\[(.+)\]\[(.+)\]\.([^\.]+)\.?(log)?/m", $name, $parse, PREG_SET_ORDER, 0)) { + $problemData = problemGet($parse[0][3], $_SESSION["id"] === "admin"); + $problemName = null; + $problemPoint = null; + + if ($problemData !== PROBLEM_ERROR_IDREJECT && $problemData !== PROBLEM_ERROR_DISABLED) { + $problemName = $problemData["name"]; + $problemPoint = $problemData["point"]; + } + + return Array( + "id" => $parse[0][1], + "user" => $parse[0][2], + "problem" => $parse[0][3], + "problemName" => $problemName, + "problemPoint" => $problemPoint, + "extension" => $parse[0][4], + "name" => $parse[0][3] .".". $parse[0][4], + "isLogFile" => isset($parse[0][5]) + ); + } + + return false; + } \ No newline at end of file