From 2a104335239c866835839fe0286fe733576c9352 Mon Sep 17 00:00:00 2001 From: Michael Vasseur <14887731+vmcj@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:08:45 +0100 Subject: [PATCH 1/3] Allow teams to download all samples/attachments We already allow this over the API so use the same URL. --- .../templates/partials/problem_list.html.twig | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig index b85f2437df..62929bc5bd 100644 --- a/webapp/templates/partials/problem_list.html.twig +++ b/webapp/templates/partials/problem_list.html.twig @@ -4,16 +4,21 @@

{{ contest.name | default('Contest') }} problems - {% if contest and show_contest_problemset and contest.contestProblemsetType is not empty %} - {% if contest_problemset_add_cid %} - {% set contest_problemset_url = path(contest_problemset_path, {'cid': contest.cid}) %} - {% else %} - {% set contest_problemset_url = path(contest_problemset_path) %} + {% if contest and show_contest_problemset %} + {% if contest.contestProblemsetType is not empty %} + {% if contest_problemset_add_cid %} + {% set contest_problemset_url = path(contest_problemset_path, {'cid': contest.cid}) %} + {% else %} + {% set contest_problemset_url = path(contest_problemset_path) %} + {% endif %} + + + problemset + {% endif %} - - - problemset + + samples {% endif %}

From c6097fe4a573f369ef6929b94b48373e2ef82eca Mon Sep 17 00:00:00 2001 From: Michael Vasseur <14887731+vmcj@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:26:14 +0100 Subject: [PATCH 2/3] Check if there would be any attachments/samples in the contest samples.zip We always try to exit early and skip the actual adding. As checking and creation are more or less the same code we wrap the check inside the actual creation to prevent duplication. --- webapp/src/Service/DOMJudgeService.php | 53 +++++++++++++++++++++----- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 5101285bf8..a9ee563268 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -830,7 +830,7 @@ public function getSamplesZipContent(ContestProblem $contestProblem): string return $zipFileContents; } - protected function addSamplesToZip(ZipArchive $zip, ContestProblem $problem, ?string $directory = null): void + protected function addSamplesToZip(?ZipArchive $zip, ContestProblem $problem, ?string $directory = null, bool $fullZip = true): bool { /** @var Testcase[] $testcases */ $testcases = $this->em->createQueryBuilder() @@ -849,6 +849,9 @@ protected function addSamplesToZip(ZipArchive $zip, ContestProblem $problem, ?st ->getResult(); foreach ($testcases as $index => $testcase) { + if (!$fullZip) { + return true; + } foreach (['input', 'output'] as $type) { $extension = Testcase::EXTENSION_MAPPING[$type]; @@ -870,6 +873,7 @@ protected function addSamplesToZip(ZipArchive $zip, ContestProblem $problem, ?st $zip->addFromString($filename, $content); } } + return false; } public function getSamplesZipStreamedResponse(ContestProblem $contestProblem): StreamedResponse @@ -879,7 +883,7 @@ public function getSamplesZipStreamedResponse(ContestProblem $contestProblem): S return Utils::streamAsBinaryFile($zipFileContent, $outputFilename, 'zip'); } - public function getSamplesZipForContest(Contest $contest): StreamedResponse + public function helperSamplesZipForContest(Contest $contest, bool $fullZip): StreamedResponse|bool { // Note, we reload the contest with the problems and attachments, to reduce the number of queries // We do not load the testcases here since addSamplesToZip loads them @@ -896,30 +900,42 @@ public function getSamplesZipForContest(Contest $contest): StreamedResponse ->getQuery() ->getSingleResult(); - $zip = new ZipArchive(); - if (!($tempFilename = tempnam($this->getDomjudgeTmpDir(), "export-"))) { - throw new ServiceUnavailableHttpException(null, 'Could not create temporary file.'); - } + $zip = null; + if ($fullZip) { + $zip = new ZipArchive(); + if (!($tempFilename = tempnam($this->getDomjudgeTmpDir(), "export-"))) { + throw new ServiceUnavailableHttpException(null, 'Could not create temporary file.'); + } - $res = $zip->open($tempFilename, ZipArchive::OVERWRITE); - if ($res !== true) { - throw new ServiceUnavailableHttpException(null, 'Could not create temporary zip file.'); + $res = $zip->open($tempFilename, ZipArchive::OVERWRITE); + if ($res !== true) { + throw new ServiceUnavailableHttpException(null, 'Could not create temporary zip file.'); + } } /** @var ContestProblem $problem */ foreach ($contest->getProblems() as $problem) { // We don't include the samples for interactive problems. if (!$problem->getProblem()->isInteractiveProblem()) { - $this->addSamplesToZip($zip, $problem, $problem->getShortname()); + $samplesFound = $this->addSamplesToZip($zip, $problem, $problem->getShortname(), fullZip: $fullZip);; + if (!$fullZip && $samplesFound) { + return true; + } } if ($problem->getProblem()->getProblemstatementType()) { + if (!$fullZip) { + return true; + } $filename = sprintf('%s/statement.%s', $problem->getShortname(), $problem->getProblem()->getProblemstatementType()); $zip->addFromString($filename, $problem->getProblem()->getProblemstatement()); } /** @var ProblemAttachment $attachment */ foreach ($problem->getProblem()->getAttachments() as $attachment) { + if (!$fullZip) { + return true; + } $filename = sprintf('%s/attachments/%s', $problem->getShortname(), $attachment->getName()); $zip->addFromString($filename, $attachment->getContent()->getContent()); if ($attachment->getContent()->isExecutable()) { @@ -934,10 +950,17 @@ public function getSamplesZipForContest(Contest $contest): StreamedResponse } if ($contest->getContestProblemsetType()) { + if (!$fullZip) { + return true; + } $filename = sprintf('contest.%s', $contest->getContestProblemsetType()); $zip->addFromString($filename, $contest->getContestProblemset()); } + if (!$fullZip) { + return false; + } + $zip->close(); $zipFileContents = file_get_contents($tempFilename); unlink($tempFilename); @@ -945,6 +968,16 @@ public function getSamplesZipForContest(Contest $contest): StreamedResponse return Utils::streamAsBinaryFile($zipFileContents, 'samples.zip', 'zip'); } + public function checkIfSamplesZipForContest(Contest $contest): bool + { + return self::helperSamplesZipForContest($contest, fullZip: false); + } + + public function getSamplesZipForContest(Contest $contest): StreamedResponse + { + return self::helperSamplesZipForContest($contest, fullZip: true); + } + /** * @throws NonUniqueResultException */ From 75f5988f3a73afd94fed030e974e50fc1f76ca05 Mon Sep 17 00:00:00 2001 From: Michael Vasseur <14887731+vmcj@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:38:58 +0100 Subject: [PATCH 3/3] Only show contest samples zip when there are attachments --- webapp/src/Service/DOMJudgeService.php | 1 + webapp/templates/partials/problem_list.html.twig | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index a9ee563268..0712c68ee9 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -1130,6 +1130,7 @@ public function getTwigDataForProblemsAction( 'problems' => $problems, 'samples' => $samples, 'showLimits' => $showLimits, + 'showSamples' => $this->checkIfSamplesZipForContest($contest), 'defaultMemoryLimit' => $defaultMemoryLimit, 'timeFactorDiffers' => $timeFactorDiffers, 'clarifications' => $clars, diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig index 62929bc5bd..7c042652dc 100644 --- a/webapp/templates/partials/problem_list.html.twig +++ b/webapp/templates/partials/problem_list.html.twig @@ -17,9 +17,11 @@ problemset {% endif %} - - samples - + {% if showSamples %} + + samples + + {% endif %} {% endif %}