From fe438d73c8f5facd07c8e6f2859c27cf552cf0f8 Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Tue, 3 Dec 2024 22:24:51 +0100 Subject: [PATCH 1/7] Remove unused functions: don't expose random stuff --- webapp/src/Service/DOMJudgeService.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 0d9a5512f5..276cf40482 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -100,11 +100,6 @@ public function __construct( protected string $vendorDir, ) {} - public function getEntityManager(): EntityManagerInterface - { - return $this->em; - } - /** * Return all the contests that are currently active indexed by contest ID. * @@ -454,11 +449,6 @@ public function getUpdates(): array ]; } - public function getHttpKernel(): HttpKernelInterface - { - return $this->httpKernel; - } - /** * Run the given callable with all roles. * @@ -654,7 +644,7 @@ public function internalApiRequest(string $url, string $method = Request::METHOD if ($this->requestStack->getCurrentRequest() && $this->requestStack->getCurrentRequest()->hasSession()) { $request->setSession($this->requestStack->getSession()); } - $response = $this->getHttpKernel()->handle($request, HttpKernelInterface::SUB_REQUEST); + $response = $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST); $status = $response->getStatusCode(); if ($status < 200 || $status >= 300) { From 0ac65d741bc3a986611019eee6ca12f5eac94b1e Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Tue, 3 Dec 2024 22:27:21 +0100 Subject: [PATCH 2/7] Clarify that it doesn't iterate over all roles, but enables them --- webapp/src/Service/DOMJudgeService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 276cf40482..b2914afef2 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -450,7 +450,7 @@ public function getUpdates(): array } /** - * Run the given callable with all roles. + * Run the given callable with all roles enabled. * * This will result in all calls to checkrole() to return true. */ From ba5462d10a07f670c39bcee624f777f12cd3c487 Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Tue, 3 Dec 2024 23:09:35 +0100 Subject: [PATCH 3/7] Move json{En,De}code functions to Utils They don't depend on any DOMjudge internals. --- webapp/src/Command/CallApiActionCommand.php | 3 ++- .../src/Controller/API/ContestController.php | 4 +-- .../Controller/API/JudgehostController.php | 4 +-- .../src/Controller/API/LanguageController.php | 3 ++- .../src/Controller/API/ProblemController.php | 3 ++- .../src/Controller/Jury/BalloonController.php | 3 ++- .../src/Controller/Jury/ContestController.php | 6 ++--- .../Jury/ExternalContestController.php | 2 +- .../Jury/InternalErrorController.php | 2 +- .../Controller/Jury/JuryMiscController.php | 3 ++- .../Controller/Jury/RejudgingController.php | 6 ++--- .../Controller/Jury/SubmissionController.php | 12 ++++----- webapp/src/Service/ConfigurationService.php | 3 ++- webapp/src/Service/DOMJudgeService.php | 25 +++---------------- webapp/src/Service/EventLogService.php | 16 ++++++------ webapp/src/Service/ImportExportService.php | 2 +- webapp/src/Service/ImportProblemService.php | 4 +-- webapp/src/Service/ScoreboardService.php | 4 +-- webapp/src/Utils/Utils.php | 17 +++++++++++++ 19 files changed, 64 insertions(+), 58 deletions(-) diff --git a/webapp/src/Command/CallApiActionCommand.php b/webapp/src/Command/CallApiActionCommand.php index a1b9aa6ba6..47d98c2671 100644 --- a/webapp/src/Command/CallApiActionCommand.php +++ b/webapp/src/Command/CallApiActionCommand.php @@ -4,6 +4,7 @@ use App\Entity\User; use App\Service\DOMJudgeService; +use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -114,7 +115,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($json = $input->getOption('json')) { - $data = array_merge($data, $this->dj->jsonDecode($json)); + $data = array_merge($data, Utils::jsonDecode($json)); } foreach ($input->getOption('file') as $fileItem) { diff --git a/webapp/src/Controller/API/ContestController.php b/webapp/src/Controller/API/ContestController.php index 245490ae5c..388287c6b3 100644 --- a/webapp/src/Controller/API/ContestController.php +++ b/webapp/src/Controller/API/ContestController.php @@ -114,7 +114,7 @@ public function addContestAction(Request $request): string return $cid; } } elseif ($jsonFile) { - $data = $this->dj->jsonDecode(file_get_contents($jsonFile->getRealPath())); + $data = Utils::jsonDecode(file_get_contents($jsonFile->getRealPath())); if ($this->importExportService->importContestData($data, $message, $cid)) { return $cid; } @@ -870,7 +870,7 @@ public function getEventFeedAction( $result['time'] = Utils::absTime($event->getEventtime()); } - echo $this->dj->jsonEncode($result) . "\n"; + echo Utils::jsonEncode($result) . "\n"; ob_flush(); flush(); $lastUpdate = Utils::now(); diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 23158e1c1a..705a08542d 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -734,7 +734,7 @@ public function internalErrorAction(Request $request): ?int } } - $disabled = $this->dj->jsonDecode($disabled); + $disabled = Utils::jsonDecode($disabled); /** @var Contest|null $contest */ $contest = null; @@ -770,7 +770,7 @@ public function internalErrorAction(Request $request): ?int ->andWhere('e.disabled = :disabled') ->andWhere('e.status = :status') ->setParameter('description', $description) - ->setParameter('disabled', $this->dj->jsonEncode($disabled)) + ->setParameter('disabled', Utils::jsonEncode($disabled)) ->setParameter('status', 'open') ->setMaxResults(1); diff --git a/webapp/src/Controller/API/LanguageController.php b/webapp/src/Controller/API/LanguageController.php index 0415d4120b..ecec18c1d9 100644 --- a/webapp/src/Controller/API/LanguageController.php +++ b/webapp/src/Controller/API/LanguageController.php @@ -5,6 +5,7 @@ use App\Entity\ExecutableFile; use App\Entity\ImmutableExecutable; use App\Entity\Language; +use App\Utils\Utils; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\QueryBuilder; use FOS\RestBundle\Controller\Annotations as Rest; @@ -139,7 +140,7 @@ public function configureLanguagesAction(Request $request): Response if (!$jsonFile) { throw new BadRequestHttpException('No JSON file supplied.'); } - $newLanguages = $this->dj->jsonDecode(file_get_contents($jsonFile->getRealPath())); + $newLanguages = Utils::jsonDecode(file_get_contents($jsonFile->getRealPath())); // Disable submission for all current languages, we will enable it for all new languages below. $curLanguages = $this->em->getRepository(Language::class)->findAll(); diff --git a/webapp/src/Controller/API/ProblemController.php b/webapp/src/Controller/API/ProblemController.php index a281237137..f4e07291b0 100644 --- a/webapp/src/Controller/API/ProblemController.php +++ b/webapp/src/Controller/API/ProblemController.php @@ -13,6 +13,7 @@ use App\Service\EventLogService; use App\Service\ImportExportService; use App\Service\ImportProblemService; +use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\QueryBuilder; @@ -108,7 +109,7 @@ public function addProblemsAction(Request $request): array } $message = "Error while adding problems"; if (!empty($messages)) { - $message .= ': ' . $this->dj->jsonEncode($messages); + $message .= ': ' . Utils::jsonEncode($messages); } throw new BadRequestHttpException($message); } diff --git a/webapp/src/Controller/Jury/BalloonController.php b/webapp/src/Controller/Jury/BalloonController.php index 5726d5032f..9098772310 100644 --- a/webapp/src/Controller/Jury/BalloonController.php +++ b/webapp/src/Controller/Jury/BalloonController.php @@ -9,6 +9,7 @@ use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use App\Service\EventLogService; +use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\ExpressionLanguage\Expression; @@ -77,7 +78,7 @@ public function indexAction(BalloonService $balloonService): Response } // Load preselected filters - $filters = $this->dj->jsonDecode((string)$this->dj->getCookie('domjudge_balloonsfilter') ?: '[]'); + $filters = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_balloonsfilter') ?: '[]'); $haveFilters = $this->dj->getCookie('domjudge_balloonsfilter') != null; $filteredAffiliations = []; $filteredLocations = []; diff --git a/webapp/src/Controller/Jury/ContestController.php b/webapp/src/Controller/Jury/ContestController.php index aafed2ecfa..1e4c8b7bc9 100644 --- a/webapp/src/Controller/Jury/ContestController.php +++ b/webapp/src/Controller/Jury/ContestController.php @@ -708,7 +708,7 @@ public function prefetchAction(Request $request, int $contestId): Response // TODO: dedup here? $compareExec = $this->dj->getImmutableCompareExecutable($contestProblem); $runExec = $this->dj->getImmutableRunExecutable($contestProblem); - $runConfig = $this->dj->jsonEncode( + $runConfig = Utils::jsonEncode( [ 'hash' => $runExec->getHash(), 'combined_run_compare' => $problem->getCombinedRunCompare(), @@ -720,7 +720,7 @@ public function prefetchAction(Request $request, int $contestId): Response ->setJudgehost($judgehost) ->setPriority(JudgeTask::PRIORITY_DEFAULT) ->setCompareScriptId($compareExec->getImmutableExecId()) - ->setCompareConfig($this->dj->jsonEncode(['hash' => $compareExec->getHash()])) + ->setCompareConfig(Utils::jsonEncode(['hash' => $compareExec->getHash()])) ->setRunScriptId($runExec->getImmutableExecId()) ->setRunConfig($runConfig); $this->em->persist($judgeTask); @@ -741,7 +741,7 @@ public function prefetchAction(Request $request, int $contestId): Response ->setJudgehost($judgehost) ->setPriority(JudgeTask::PRIORITY_DEFAULT) ->setCompileScriptId($compileExec->getImmutableExecId()) - ->setCompileConfig($this->dj->jsonEncode(['hash' => $compileExec->getHash()])); + ->setCompileConfig(Utils::jsonEncode(['hash' => $compileExec->getHash()])); $this->em->persist($judgeTask); $cnt++; } diff --git a/webapp/src/Controller/Jury/ExternalContestController.php b/webapp/src/Controller/Jury/ExternalContestController.php index 4fe4a3c5e1..93096f18fd 100644 --- a/webapp/src/Controller/Jury/ExternalContestController.php +++ b/webapp/src/Controller/Jury/ExternalContestController.php @@ -102,7 +102,7 @@ public function indexAction(Request $request): Response $this->sourceService->setSource($externalContestSource); // Load preselected filters - $filters = $this->dj->jsonDecode((string)$this->dj->getCookie('domjudge_external_source_filter') ?: '[]'); + $filters = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_external_source_filter') ?: '[]'); // Build the filter form. $form = $this->createForm(ExternalSourceWarningsFilterType::class, $filters); diff --git a/webapp/src/Controller/Jury/InternalErrorController.php b/webapp/src/Controller/Jury/InternalErrorController.php index ddb9ed840f..2b8bc03417 100644 --- a/webapp/src/Controller/Jury/InternalErrorController.php +++ b/webapp/src/Controller/Jury/InternalErrorController.php @@ -162,7 +162,7 @@ public function handleAction(Request $request, ?Profiler $profiler, int $errorId if ($request->isXmlHttpRequest()) { $profiler?->disable(); $progressReporter = function (int $progress, string $log, ?string $message = null) { - echo $this->dj->jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => $message]); + echo Utils::jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => $message]); ob_flush(); flush(); }; diff --git a/webapp/src/Controller/Jury/JuryMiscController.php b/webapp/src/Controller/Jury/JuryMiscController.php index c2f6018de9..8d3913989f 100644 --- a/webapp/src/Controller/Jury/JuryMiscController.php +++ b/webapp/src/Controller/Jury/JuryMiscController.php @@ -15,6 +15,7 @@ use App\Service\DOMJudgeService; use App\Service\EventLogService; use App\Service\ScoreboardService; +use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\Expr\Join; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -213,7 +214,7 @@ public function refreshCacheAction(Request $request, ScoreboardService $scoreboa if ($request->isXmlHttpRequest() && $request->isMethod('POST')) { $progressReporter = function (int $progress, string $log, ?string $message = null) { - echo $this->dj->jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => $message]); + echo Utils::jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => $message]); ob_flush(); flush(); }; diff --git a/webapp/src/Controller/Jury/RejudgingController.php b/webapp/src/Controller/Jury/RejudgingController.php index 1efa99c95d..4698e33a89 100644 --- a/webapp/src/Controller/Jury/RejudgingController.php +++ b/webapp/src/Controller/Jury/RejudgingController.php @@ -449,7 +449,7 @@ public function finishAction( if ($request->isXmlHttpRequest()) { $progressReporter = function (int $progress, string $log, ?string $message = null) { - echo $this->dj->jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => htmlspecialchars($message ?? '')]); + echo Utils::jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'message' => htmlspecialchars($message ?? '')]); ob_flush(); flush(); }; @@ -537,7 +537,7 @@ public function addAction(Request $request, FormFactoryInterface $formFactory): } if ($isCreateRejudgingAjax) { $progressReporter = function (int $progress, string $log, ?string $redirect = null) { - echo $this->dj->jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'redirect' => $redirect]); + echo Utils::jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'redirect' => $redirect]); ob_flush(); flush(); }; @@ -722,7 +722,7 @@ public function createAction(Request $request): Response } $progressReporter = function (int $progress, string $log, ?string $redirect = null) { - echo $this->dj->jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'redirect' => $redirect]); + echo Utils::jsonEncode(['progress' => $progress, 'log' => htmlspecialchars($log), 'redirect' => $redirect]); ob_flush(); flush(); }; diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 20f40e2168..b91a82fb3b 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -136,7 +136,7 @@ public function indexAction( } // Load preselected filters - $filters = $this->dj->jsonDecode((string)$this->dj->getCookie('domjudge_submissionsfilter') ?: '[]'); + $filters = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_submissionsfilter') ?: '[]'); $results = array_keys($this->dj->getVerdicts(['final', 'in_progress'])); @@ -257,7 +257,7 @@ public function viewAction( ->getQuery() ->getResult(); $timelimits = array_map(function (JudgeTask $task) { - return $this->dj->jsonDecode($task->getRunConfig())['time_limit']; + return Utils::jsonDecode($task->getRunConfig())['time_limit']; }, $judgeTasks); } @@ -626,7 +626,7 @@ public function requestFullDebug(Request $request, Judging $jid): RedirectRespon ->setJobId($jid->getJudgingid()) ->setUuid($jid->getUuid()) ->setRunScriptId($executable->getImmutableExecId()) - ->setRunConfig($this->dj->jsonEncode(['hash' => $executable->getHash()])); + ->setRunConfig(Utils::jsonEncode(['hash' => $executable->getHash()])); $this->em->persist($judgeTask); } $this->em->flush(); @@ -1257,8 +1257,8 @@ public function createJudgeTasks(string $submitId): RedirectResponse */ private function maybeGetErrors(string $type, string $expectedConfigString, string $observedConfigString, array &$allErrors): void { - $expectedConfig = $this->dj->jsonDecode($expectedConfigString); - $observedConfig = $this->dj->jsonDecode($observedConfigString); + $expectedConfig = Utils::jsonDecode($expectedConfigString); + $observedConfig = Utils::jsonDecode($observedConfigString); $errors = []; foreach (array_keys($expectedConfig) as $k) { if (!array_key_exists($k, $observedConfig)) { @@ -1271,7 +1271,7 @@ private function maybeGetErrors(string $type, string $expectedConfigString, stri // Silently ignore. } else { $errors[] = '- ' . preg_replace('/_/', ' ', $k) . ': ' - . $this->dj->jsonEncode($observedConfig[$k]) . ' → ' . $this->dj->jsonEncode($expectedConfig[$k]); + . Utils::jsonEncode($observedConfig[$k]) . ' → ' . Utils::jsonEncode($expectedConfig[$k]); } } } diff --git a/webapp/src/Service/ConfigurationService.php b/webapp/src/Service/ConfigurationService.php index 6fe72c340f..c215a154d8 100644 --- a/webapp/src/Service/ConfigurationService.php +++ b/webapp/src/Service/ConfigurationService.php @@ -7,6 +7,7 @@ use App\Entity\Configuration; use App\Entity\Executable; use App\Entity\Judging; +use App\Utils\Utils; use BackedEnum; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; @@ -269,7 +270,7 @@ public function saveChanges( } if (!isset($errors[$specName])) { if ($optionToSet->getValue() != $oldValue) { - $valJson = $dj->jsonEncode($optionToSet->getValue()); + $valJson = Utils::jsonEncode($optionToSet->getValue()); $dj->auditlog('configuration', $specName, 'updated', $valJson); if ($optionIsNew) { $this->em->persist($optionToSet); diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index b2914afef2..1c0a90a4df 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -522,23 +522,6 @@ public function alert(string $messageType, string $description = ''): void system(sprintf('%s %s %s &', $alert, escapeshellarg($messageType), escapeshellarg($description))); } - /** - * Decode a JSON string with our preferred settings. - * @return mixed - */ - public function jsonDecode(string $str) - { - return json_decode($str, true, 512, JSON_THROW_ON_ERROR); - } - - /** - * Encode a JSON string with our preferred settings. - */ - public function jsonEncode(mixed $data): string - { - return json_encode($data, JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); - } - /** * Dis- or re-enable what caused an internal error. * @@ -664,7 +647,7 @@ public function internalApiRequest(string $url, string $method = Request::METHOD return null; } - return $this->jsonDecode($content); + return Utils::jsonDecode($content); } public function getDomjudgeEtcDir(): string @@ -1454,7 +1437,7 @@ public function getRunConfig(ContestProblem $problem, Submission $submission, in } $runExecutable = $this->getImmutableRunExecutable($problem); - return $this->jsonEncode( + return Utils::jsonEncode( [ 'time_limit' => $problem->getProblem()->getTimelimit() * $submission->getLanguage()->getTimeFactor(), 'memory_limit' => $memoryLimit, @@ -1471,7 +1454,7 @@ public function getRunConfig(ContestProblem $problem, Submission $submission, in public function getCompareConfig(ContestProblem $problem): string { $compareExecutable = $this->getImmutableCompareExecutable($problem); - return $this->jsonEncode( + return Utils::jsonEncode( [ 'script_timelimit' => $this->config->get('script_timelimit'), 'script_memory_limit' => $this->config->get('script_memory_limit'), @@ -1486,7 +1469,7 @@ public function getCompareConfig(ContestProblem $problem): string public function getCompileConfig(Submission $submission): string { $compileExecutable = $submission->getLanguage()->getCompileExecutable()->getImmutableExecutable(); - return $this->jsonEncode( + return Utils::jsonEncode( [ 'script_timelimit' => $this->config->get('script_timelimit'), 'script_memory_limit' => $this->config->get('script_memory_limit'), diff --git a/webapp/src/Service/EventLogService.php b/webapp/src/Service/EventLogService.php index f059e09e5e..1a21f5929a 100644 --- a/webapp/src/Service/EventLogService.php +++ b/webapp/src/Service/EventLogService.php @@ -183,8 +183,8 @@ public function log( $jsonPassed = isset($json); // Make a combined string to keep track of the data ID's - $dataidsCombined = $this->dj->jsonEncode($dataIds); - $idsCombined = $ids === null ? null : (is_array($ids) ? $this->dj->jsonEncode($ids) : $ids); + $dataidsCombined = Utils::jsonEncode($dataIds); + $idsCombined = $ids === null ? null : (is_array($ids) ? Utils::jsonEncode($ids) : $ids); $this->logger->debug( "EventLogService::log arguments: '%s' '%s' '%s' '%s' '%s' '%s'", @@ -224,7 +224,7 @@ public function log( } if ($ids === [null] && $json !== null) { - $data = $this->dj->jsonDecode($json); + $data = Utils::jsonDecode($json); if (!empty($data['id'])) { $ids = [$data['id']]; } @@ -234,14 +234,14 @@ public function log( $ids = [$ids]; } - $idsCombined = $this->dj->jsonEncode($ids); + $idsCombined = Utils::jsonEncode($ids); // State is a special case, as it works without an ID if ($type !== 'state' && count(array_filter($ids)) !== count($dataIds)) { $this->logger->warning( "EventLogService::log API ID not specified or inferred ". "from data for type %s and data ID's '%s'", - [ $type, $this->dj->jsonEncode($dataIds) ] + [ $type, Utils::jsonEncode($dataIds) ] ); return; } @@ -337,7 +337,7 @@ public function log( $now = sprintf('%.3f', microtime(true)); if ($jsonPassed) { - $json = $this->dj->jsonDecode($json); + $json = Utils::jsonDecode($json); } elseif (!in_array($type, ['contests', 'state'])) { // Re-index JSON so we can look up the elements by ID. $tmp = $json; @@ -557,8 +557,8 @@ protected function insertEvents( $existingEvent = $existingEvents[$event->getEndpointid()] ?? null; $existingData = $existingEvent === null ? null : - $this->dj->jsonEncode($existingEvent->getContent()); - $data = $this->dj->jsonEncode($event->getContent()); + Utils::jsonEncode($existingEvent->getContent()); + $data = Utils::jsonEncode($event->getContent()); if ($existingEvent === null || $existingData !== $data) { // Special case for state: this is always an update event if ($endpointType === 'state') { diff --git a/webapp/src/Service/ImportExportService.php b/webapp/src/Service/ImportExportService.php index 6e175668f3..493695d369 100644 --- a/webapp/src/Service/ImportExportService.php +++ b/webapp/src/Service/ImportExportService.php @@ -665,7 +665,7 @@ public function importJson(string $type, UploadedFile $file, ?string &$message = { $content = file_get_contents($file->getRealPath()); try { - $data = $this->dj->jsonDecode($content); + $data = Utils::jsonDecode($content); } catch (JsonException $e) { // Check if we can parse it as YAML try { diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index 7f3a503022..f341187c93 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -658,7 +658,7 @@ public function importZippedProblem( // Read submission details from optional file. $submission_file_string = $zip->getFromName($submission_file); $submission_details = $submission_file_string===false ? [] : - $this->dj->jsonDecode($submission_file_string); + Utils::jsonDecode($submission_file_string); $numJurySolutions = 0; for ($j = 0; $j < $zip->numFiles; $j++) { @@ -913,7 +913,7 @@ public function importProblemFromRequest(Request $request, ?int $contestId = nul $zip?->close(); } if (!empty($errors)) { - throw new BadRequestHttpException($this->dj->jsonEncode($errors)); + throw new BadRequestHttpException(Utils::jsonEncode($errors)); } return [ 'problem_id' => $probId, diff --git a/webapp/src/Service/ScoreboardService.php b/webapp/src/Service/ScoreboardService.php index 35ab9f15cc..0c1167d425 100644 --- a/webapp/src/Service/ScoreboardService.php +++ b/webapp/src/Service/ScoreboardService.php @@ -713,7 +713,7 @@ public function initializeScoreboardFilter(Request $request, ?Response $response { $scoreFilter = []; if ($this->dj->getCookie('domjudge_scorefilter')) { - $scoreFilter = $this->dj->jsonDecode((string)$this->dj->getCookie('domjudge_scorefilter')); + $scoreFilter = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_scorefilter')); } if ($request->query->has('clear')) { @@ -731,7 +731,7 @@ public function initializeScoreboardFilter(Request $request, ?Response $response $this->dj->setCookie( 'domjudge_scorefilter', - $this->dj->jsonEncode($scoreFilter), + Utils::jsonEncode($scoreFilter), 0, null, '', false, false, $response ); diff --git a/webapp/src/Utils/Utils.php b/webapp/src/Utils/Utils.php index c1ec4acb9a..6b59489979 100644 --- a/webapp/src/Utils/Utils.php +++ b/webapp/src/Utils/Utils.php @@ -949,4 +949,21 @@ public static function getTextStreamedResponse( return $response; } + + /** + * Decode a JSON string with our preferred settings. + * @return mixed + */ + public static function jsonDecode(string $str) + { + return json_decode($str, true, 512, JSON_THROW_ON_ERROR); + } + + /** + * Encode a JSON string with our preferred settings. + */ + public static function jsonEncode(mixed $data): string + { + return json_encode($data, JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); + } } From 49d347faf0ae2baec4b44a21e032c10531c94057 Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Tue, 3 Dec 2024 23:29:40 +0100 Subject: [PATCH 4/7] Move getVerdicts to ConfigurationService Also add some documentation. --- .../Controller/API/JudgementController.php | 2 +- .../API/JudgementTypeController.php | 2 +- webapp/src/Controller/API/RunController.php | 2 +- .../Controller/Jury/RejudgingController.php | 2 +- .../Jury/ShadowDifferencesController.php | 2 +- .../Controller/Jury/SubmissionController.php | 2 +- webapp/src/Form/Type/RejudgingType.php | 2 +- .../src/Form/Type/SubmissionsFilterType.php | 11 +++--- webapp/src/Service/ConfigurationService.php | 35 +++++++++++++++++++ webapp/src/Service/DOMJudgeService.php | 22 ------------ .../Service/ExternalContestSourceService.php | 4 +-- 11 files changed, 51 insertions(+), 35 deletions(-) diff --git a/webapp/src/Controller/API/JudgementController.php b/webapp/src/Controller/API/JudgementController.php index ec252ed09e..9c9de7bcee 100644 --- a/webapp/src/Controller/API/JudgementController.php +++ b/webapp/src/Controller/API/JudgementController.php @@ -44,7 +44,7 @@ public function __construct( ) { parent::__construct($entityManager, $DOMJudgeService, $config, $eventLogService); - $this->verdicts = $this->dj->getVerdicts(['final', 'error']); + $this->verdicts = $this->config->getVerdicts(['final', 'error']); } /** diff --git a/webapp/src/Controller/API/JudgementTypeController.php b/webapp/src/Controller/API/JudgementTypeController.php index 52aa5cf934..6a8d475623 100644 --- a/webapp/src/Controller/API/JudgementTypeController.php +++ b/webapp/src/Controller/API/JudgementTypeController.php @@ -85,7 +85,7 @@ public function singleAction(Request $request, string $id): JudgementType */ protected function getJudgementTypes(?array $filteredOn = null): array { - $verdicts = $this->dj->getVerdicts(['final', 'external']); + $verdicts = $this->config->getVerdicts(['final', 'external']); $result = []; foreach ($verdicts as $name => $label) { diff --git a/webapp/src/Controller/API/RunController.php b/webapp/src/Controller/API/RunController.php index c73ff8780d..386309a19f 100644 --- a/webapp/src/Controller/API/RunController.php +++ b/webapp/src/Controller/API/RunController.php @@ -46,7 +46,7 @@ public function __construct( parent::__construct($entityManager, $DOMJudgeService, $config, $eventLogService); - $this->verdicts = $this->dj->getVerdicts(); + $this->verdicts = $this->config->getVerdicts(); } /** diff --git a/webapp/src/Controller/Jury/RejudgingController.php b/webapp/src/Controller/Jury/RejudgingController.php index 4698e33a89..e73cbd76fa 100644 --- a/webapp/src/Controller/Jury/RejudgingController.php +++ b/webapp/src/Controller/Jury/RejudgingController.php @@ -243,7 +243,7 @@ public function viewAction( $todo = $todoAndDone['todo']; $done = $todoAndDone['done']; - $verdicts = $this->dj->getVerdicts(['final', 'error']); + $verdicts = $this->config->getVerdicts(['final', 'error']); $verdicts[''] = 'JE'; /* happens for aborted judgings */ $used = []; diff --git a/webapp/src/Controller/Jury/ShadowDifferencesController.php b/webapp/src/Controller/Jury/ShadowDifferencesController.php index f1f5b7a40e..ec80c8339d 100644 --- a/webapp/src/Controller/Jury/ShadowDifferencesController.php +++ b/webapp/src/Controller/Jury/ShadowDifferencesController.php @@ -83,7 +83,7 @@ public function indexAction( $this->requestStack->getSession()->save(); $contest = $this->dj->getCurrentContest(); - $verdicts = $this->dj->getVerdicts(['final', 'error', 'external', 'in_progress']); + $verdicts = $this->config->getVerdicts(['final', 'error', 'external', 'in_progress']); $used = []; $verdictTable = []; diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index b91a82fb3b..18b2446061 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -138,7 +138,7 @@ public function indexAction( // Load preselected filters $filters = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_submissionsfilter') ?: '[]'); - $results = array_keys($this->dj->getVerdicts(['final', 'in_progress'])); + $results = array_keys($this->config->getVerdicts(['final', 'in_progress'])); $data = [ 'refresh' => $refresh, diff --git a/webapp/src/Form/Type/RejudgingType.php b/webapp/src/Form/Type/RejudgingType.php index 661cbd3132..4335f75f74 100644 --- a/webapp/src/Form/Type/RejudgingType.php +++ b/webapp/src/Form/Type/RejudgingType.php @@ -107,7 +107,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->orderBy('j.hostname'), ]); - $verdicts = array_keys($this->dj->getVerdicts()); + $verdicts = array_keys($this->config->getVerdicts()); $builder->add('verdicts', ChoiceType::class, [ 'label' => 'Verdict', 'multiple' => true, diff --git a/webapp/src/Form/Type/SubmissionsFilterType.php b/webapp/src/Form/Type/SubmissionsFilterType.php index 9629cbf861..87ce7e003c 100644 --- a/webapp/src/Form/Type/SubmissionsFilterType.php +++ b/webapp/src/Form/Type/SubmissionsFilterType.php @@ -7,6 +7,7 @@ use App\Entity\Team; use App\Entity\TeamAffiliation; use App\Entity\TeamCategory; +use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; @@ -18,9 +19,11 @@ class SubmissionsFilterType extends AbstractType { - public function __construct(protected readonly DOMJudgeService $dj, protected readonly EntityManagerInterface $em) - { - } + public function __construct( + protected readonly ConfigurationService $config, + protected readonly DOMJudgeService $dj, + protected readonly EntityManagerInterface $em, + ) {} public function buildForm(FormBuilderInterface $builder, array $options): void { @@ -115,7 +118,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "attr" => ["data-filter-field" => "team-id"], ]); - $verdicts = array_keys($this->dj->getVerdicts(['final', 'error', 'in_progress'])); + $verdicts = array_keys($this->config->getVerdicts(['final', 'error', 'in_progress'])); $builder->add("result", ChoiceType::class, [ "label" => "Filter on result(s)", "multiple" => true, diff --git a/webapp/src/Service/ConfigurationService.php b/webapp/src/Service/ConfigurationService.php index c215a154d8..b69360f74f 100644 --- a/webapp/src/Service/ConfigurationService.php +++ b/webapp/src/Service/ConfigurationService.php @@ -396,4 +396,39 @@ public function addOptions(ConfigurationSpecification $item): ConfigurationSpeci } return $item; } + + /** + * Returns all possible judgement (run) verdicts, both internally + * hardcoded and from configured judgement types. Depending on the + * requirements of the context, the following groups of verdicts can be + * requests: + * - final: final verdicts supported by the system + * - error: error states that must be resolved by an admin + * - in_progress: states reported when a judging is pending a final verdict + * - external: configured verdicts when importing from an external system + * + * Verdicts are returned as an associative array of name/2-3 letter + * identifier key/value pairs. The identifiers try to adhere to + * https://ccs-specs.icpc.io/draft/contest_api#known-judgement-types + * + * @return array + */ + public function getVerdicts(array $groups = ['final']): array + { + $verdictsConfig = $this->etcDir . '/verdicts.php'; + $verdictGroups = include $verdictsConfig; + + $verdicts = []; + foreach( $groups as $group ) { + if ( $group === 'external' ) { + foreach ($this->get('external_judgement_types') as $id => $name) { + $verdicts[$name] = $id; + } + } else { + $verdicts = array_merge($verdicts, $verdictGroups[$group]); + } + } + + return $verdicts; + } } diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 1c0a90a4df..e35d62574a 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -1481,28 +1481,6 @@ public function getCompileConfig(Submission $submission): string ); } - /** - * @return array - */ - public function getVerdicts(array $groups = ['final']): array - { - $verdictsConfig = $this->getDomjudgeEtcDir() . '/verdicts.php'; - $verdictGroups = include $verdictsConfig; - - $verdicts = []; - foreach( $groups as $group ) { - if ( $group === 'external' ) { - foreach ($this->config->get('external_judgement_types') as $id => $name) { - $verdicts[$name] = $id; - } - } else { - $verdicts = array_merge($verdicts, $verdictGroups[$group]); - } - } - - return $verdicts; - } - public function getScoreboardZip( Request $request, RequestStack $requestStack, diff --git a/webapp/src/Service/ExternalContestSourceService.php b/webapp/src/Service/ExternalContestSourceService.php index 3c525ffb6c..8dd43cf696 100644 --- a/webapp/src/Service/ExternalContestSourceService.php +++ b/webapp/src/Service/ExternalContestSourceService.php @@ -269,7 +269,7 @@ public function getLastReadEventId(): ?string public function import(bool $fromStart, array $eventsToSkip, ?callable $progressReporter = null): bool { // We need the verdicts to validate judgement-types. - $this->verdicts = $this->dj->getVerdicts(['final', 'external']); + $this->verdicts = $this->config->getVerdicts(['final', 'external']); if (!$this->isValidContestSource()) { throw new LogicException('The contest source is not valid'); @@ -799,7 +799,7 @@ protected function importJudgementType(Event $event, EventData $data): void $customVerdicts = $this->config->get('external_judgement_types'); $customVerdicts[$verdict] = str_replace(' ', '-', $data->name); $this->config->saveChanges(['external_judgement_types' => $customVerdicts], $this->eventLog, $this->dj); - $this->verdicts = $this->dj->getVerdicts(['final', 'external']); + $this->verdicts = $this->config->getVerdicts(['final', 'external']); $penalty = true; $solved = false; $this->logger->warning('Judgement type %s not found locally, importing as external verdict', [$verdict]); From c802b1b93b3f045b7645e467e803e0e6206b0447 Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Tue, 3 Dec 2024 23:36:13 +0100 Subject: [PATCH 5/7] Fix broken configuration of results_remap. This was broken in 754779b4408de2beb6a1b9a4b6d. As reported by Shengliang Cai and based on a patch by @kevinjil. --- webapp/src/Service/ConfigurationService.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/src/Service/ConfigurationService.php b/webapp/src/Service/ConfigurationService.php index b69360f74f..a06e68158d 100644 --- a/webapp/src/Service/ConfigurationService.php +++ b/webapp/src/Service/ConfigurationService.php @@ -371,8 +371,7 @@ public function addOptions(ConfigurationSpecification $item): ConfigurationSpeci break; case 'results_prio': case 'results_remap': - $verdictsConfig = $this->etcDir . '/verdicts.php'; - $verdicts = include $verdictsConfig; + $verdicts = $this->getVerdicts(['final']); $item->keyOptions = ['' => '']; foreach (array_keys($verdicts) as $verdict) { $item->keyOptions[$verdict] = $verdict; From f3ec76d06a3ec06a2080ac607de63b30622a78ea Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Wed, 4 Dec 2024 23:33:33 +0100 Subject: [PATCH 6/7] Move parseMetadata function to Utils --- .../Controller/Jury/SubmissionController.php | 2 +- webapp/src/Service/DOMJudgeService.php | 18 ------------------ webapp/src/Twig/TwigExtension.php | 2 +- webapp/src/Utils/Utils.php | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 18b2446061..d05db0aa47 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -397,7 +397,7 @@ public function viewAction( $runs[] = $runResult[0]; unset($runResult[0]); if (!empty($runResult['metadata'])) { - $metadata = $this->dj->parseMetadata($runResult['metadata']); + $metadata = Utils::parseMetadata($runResult['metadata']); $runResult['output_limit'] = $metadata['output-truncated'] ?? 'n/a'; } $runResult['terminated'] = preg_match('/timelimit exceeded.*hard (wall|cpu) time/', diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index e35d62574a..8a442e6d25 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -1407,24 +1407,6 @@ public function loadTeam(string $teamId, Contest $contest): Team return $team; } - /** - * @return array - */ - public function parseMetadata(string $raw_metadata): array - { - // TODO: Reduce duplication with judgedaemon code. - $contents = explode("\n", $raw_metadata); - $res = []; - foreach ($contents as $line) { - if (str_contains($line, ":")) { - [$key, $value] = explode(":", $line, 2); - $res[$key] = trim($value); - } - } - - return $res; - } - public function getRunConfig(ContestProblem $problem, Submission $submission, int $overshoot = 0): string { $memoryLimit = $problem->getProblem()->getMemlimit(); diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 1a11d0577e..3a20dd765f 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -1209,7 +1209,7 @@ public function printMetadata(?string $metadata): string if ($metadata === null) { return ''; } - $metadata = $this->dj->parseMetadata($metadata); + $metadata = Utils::parseMetadata($metadata); return '' . ' ' . $metadata['cpu-time'] . 's CPU, ' diff --git a/webapp/src/Utils/Utils.php b/webapp/src/Utils/Utils.php index 6b59489979..b2a6c7bae6 100644 --- a/webapp/src/Utils/Utils.php +++ b/webapp/src/Utils/Utils.php @@ -966,4 +966,22 @@ public static function jsonEncode(mixed $data): string { return json_encode($data, JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); } + + /** + * @return array + */ + public static function parseMetadata(string $raw_metadata): array + { + // TODO: Reduce duplication with judgedaemon code. + $contents = explode("\n", $raw_metadata); + $res = []; + foreach ($contents as $line) { + if (str_contains($line, ":")) { + [$key, $value] = explode(":", $line, 2); + $res[$key] = trim($value); + } + } + + return $res; + } } From 8787b327a2b05eaaaf101801b269b6a814f0e907 Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Thu, 5 Dec 2024 23:16:25 +0100 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> --- webapp/src/Service/ConfigurationService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/Service/ConfigurationService.php b/webapp/src/Service/ConfigurationService.php index a06e68158d..a5c6c92ab2 100644 --- a/webapp/src/Service/ConfigurationService.php +++ b/webapp/src/Service/ConfigurationService.php @@ -418,8 +418,8 @@ public function getVerdicts(array $groups = ['final']): array $verdictGroups = include $verdictsConfig; $verdicts = []; - foreach( $groups as $group ) { - if ( $group === 'external' ) { + foreach ($groups as $group) { + if ($group === 'external') { foreach ($this->get('external_judgement_types') as $id => $name) { $verdicts[$name] = $id; }