From 2287da4b60d2df79a0f7563ea3f768e1b3d12562 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Mon, 10 Nov 2025 18:50:25 +0100
Subject: [PATCH 1/5] Hide already solved problems for teams
This moves the already solved problems under a new header, the teams can still
visit those but it hides those by default to make it easier for teams to focus
on the next problem.
In case we like this we should check if the function should actually be public.
---
.../src/Controller/Team/ProblemController.php | 5 +-
webapp/src/Service/DOMJudgeService.php | 18 +-
webapp/src/Service/ScoreboardService.php | 2 +-
.../templates/partials/problem_list.html.twig | 353 +++++++++---------
4 files changed, 203 insertions(+), 175 deletions(-)
diff --git a/webapp/src/Controller/Team/ProblemController.php b/webapp/src/Controller/Team/ProblemController.php
index 9374ee46b4..bb36bcfecd 100644
--- a/webapp/src/Controller/Team/ProblemController.php
+++ b/webapp/src/Controller/Team/ProblemController.php
@@ -8,6 +8,7 @@
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use App\Service\EventLogService;
+use App\Service\ScoreboardService;
use App\Service\StatisticsService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
@@ -32,6 +33,7 @@ public function __construct(
DOMJudgeService $dj,
protected readonly ConfigurationService $config,
protected readonly StatisticsService $stats,
+ protected readonly ScoreboardService $scoreboard,
protected readonly EventLogService $eventLogService,
EntityManagerInterface $em,
KernelInterface $kernel,
@@ -46,8 +48,9 @@ public function __construct(
public function problemsAction(): Response
{
$teamId = $this->dj->getUser()->getTeam()->getTeamid();
+ $cache = $this->scoreboard->getScorecache($this->dj->getCurrentContest(), $this->dj->getUser()->getTeam());
return $this->render('team/problems.html.twig',
- $this->dj->getTwigDataForProblemsAction($this->stats, $teamId));
+ $this->dj->getTwigDataForProblemsAction($this->stats, $teamId, cache: $cache));
}
diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php
index 5101285bf8..c23163ad2e 100644
--- a/webapp/src/Service/DOMJudgeService.php
+++ b/webapp/src/Service/DOMJudgeService.php
@@ -1009,6 +1009,7 @@ public function getContestStats(Contest $contest): ContestStatus
}
/**
+ * @param ScoreCache[]|null $cache
* @return array{'problems': ContestProblem[], 'samples': string[], 'showLimits': bool,
* 'defaultMemoryLimit': int, 'timeFactorDiffers': bool,
* 'stats': array{'numBuckets': int, 'maxBucketSizeCorrect': int,
@@ -1020,7 +1021,8 @@ public function getContestStats(Contest $contest): ContestStatus
public function getTwigDataForProblemsAction(
StatisticsService $statistics,
?int $teamId = null,
- bool $forJury = false
+ bool $forJury = false,
+ ?array $cache = null
): array {
$contest = isset($teamId) ? $this->getCurrentContest($teamId) : $this->getCurrentContest(onlyPublic: !$forJury);
$showLimits = (bool)$this->config->get('show_limits_on_team_page');
@@ -1093,8 +1095,20 @@ public function getTwigDataForProblemsAction(
}
}
+ $allProblems = [null => [], 'solved' => []];
+ if ($cache) {
+ foreach ($cache as $ind => $cachedProblem) {
+ if ($cachedProblem->getIsCorrect(true)) {
+ $allProblems['solved'][] = $problems[$ind];
+ } else {
+ $allProblems[null][] = $problems[$ind];
+ }
+ }
+ } else {
+ $allProblems = [null => $problems];
+ }
$data = [
- 'problems' => $problems,
+ 'allproblems' => $allProblems,
'samples' => $samples,
'showLimits' => $showLimits,
'defaultMemoryLimit' => $defaultMemoryLimit,
diff --git a/webapp/src/Service/ScoreboardService.php b/webapp/src/Service/ScoreboardService.php
index db9e99ecd7..144b40f3a6 100644
--- a/webapp/src/Service/ScoreboardService.php
+++ b/webapp/src/Service/ScoreboardService.php
@@ -1078,7 +1078,7 @@ protected function getCategories(bool $jury): array
* Get the scorecache used to calculate the scoreboard.
* @return ScoreCache[]
*/
- protected function getScorecache(Contest $contest, ?Team $team = null): array
+ public function getScorecache(Contest $contest, ?Team $team = null): array
{
$queryBuilder = $this->em->createQueryBuilder()
->from(ScoreCache::class, 's')
diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig
index b85f2437df..8a211cf1c1 100644
--- a/webapp/templates/partials/problem_list.html.twig
+++ b/webapp/templates/partials/problem_list.html.twig
@@ -18,193 +18,204 @@
{% endif %}
-{% if problems is empty %}
-
No problem texts available at this point.
-{% else %}
-
- {% if show_jury_warning is defined and show_jury_warning %}
-
- This is a preview of how the page will look like for teams and the public after the contest has started.
-
+{% for identifier, problems in allproblems %}
+ {% if problems is empty %}
+ {% if not identifier %}
+
No problem texts available at this point.
{% endif %}
+ {% else %}
+
+ {% if show_jury_warning is defined and show_jury_warning %}
+
+ This is a preview of how the page will look like for teams and the public after the contest has started.
+
+ {% endif %}
-
- {% for problem in problems %}
-
- {% set numsamples = samples[problem.probid] %}
- {% if problem.problem.interactiveProblem %}
- {% set numsamples = 0 %}
- {% endif %}
-
-
-
- {{ problem | problemBadge }}
-
-
- {{ problem.problem.name }}
-
- {% if showLimits %}
-
- Limits: {{ problem.problem.timelimit }}
- second
- {%- if problem.problem.timelimit > 1 %}s{% endif %}
- {%- if timeFactorDiffers -%}
- *
- {% endif %}
- /
- {{ ((problem.problem.memlimit | default(defaultMemoryLimit)) * 1024) | printSize }}
-
- {% endif %}
- {% if problem.problem.languages | length != 0 %}
+ {% if identifier %}
+
+ Already solved problems
+
+
+ {% endif%}
+
+
+ {% for problem in problems %}
+
+ {% set numsamples = samples[problem.probid] %}
+ {% if problem.problem.interactiveProblem %}
+ {% set numsamples = 0 %}
+ {% endif %}
+
+
+
+ {{ problem | problemBadge }}
+
+
+ {{ problem.problem.name }}
+
+ {% if showLimits %}
+
+ Limits: {{ problem.problem.timelimit }}
+ second
+ {%- if problem.problem.timelimit > 1 %}s{% endif %}
+ {%- if timeFactorDiffers -%}
+ *
+ {% endif %}
+ /
+ {{ ((problem.problem.memlimit | default(defaultMemoryLimit)) * 1024) | printSize }}
+
+ {% endif %}
+ {% if problem.problem.languages | length != 0 %}
+
+ Language{% if problem.problem.languages | length > 1 %}s{% endif %}:
+ {% for lang in problem.problem.languages %}
+ {{ lang.name }}{% if not loop.last %}, {% endif %}
+ {% endfor %}
+
+ {% endif %}
- Language{% if problem.problem.languages | length > 1 %}s{% endif %}:
- {% for lang in problem.problem.languages %}
- {{ lang.name }}{% if not loop.last %}, {% endif %}
- {% endfor %}
+ Type: {{ problem.problem.typesAsString }}
- {% endif %}
-
- Type: {{ problem.problem.typesAsString }}
-
- {% if stats is defined %}
-
- {% for correct in [true, false] %}
-
- {% for bucket in 0..stats.numBuckets - 1 %}
- {% if correct %}
- {% set index = 'correct' %}
- {% set maxBucketSize = stats.maxBucketSizeCorrect %}
- {% else %}
- {% set index = 'incorrect' %}
- {% set maxBucketSize = stats.maxBucketSizeIncorrect %}
- {% endif %}
- {% set stat = stats.problems[problem.problem.probid][index][bucket] %}
- {% set count = stat.count %}
- {% if maxBucketSize == 0 %}
- {% set bucket = 0 %}
- {% else %}
- {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
- {% endif %}
- {% if count == 1 %}
- {% set submissionText = 'submission' %}
- {% else %}
- {% set submissionText = 'submissions' %}
- {% endif %}
- {% if not contest.freezeData.showFinal and contest.freezetime and stat.end.timestamp >= contest.freezetime %}
- {% set maxBucketSize = max(1, stats.maxBucketSizeCorrect, stats.maxBucketSizeIncorrect) %}
- {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
- {% set itemClass = 'frozen' ~ '-' ~ bucket %}
- {% set label = count ~ ' ' ~ submissionText ~ ' in freeze' %}
- {% else %}
- {% set itemClass = index ~ '-' ~ bucket %}
- {% set label = count ~ ' ' ~ index ~ ' ' ~ submissionText %}
- {% endif %}
-
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
- {% endif %}
-
-
- {% if show_submit_button | default(false) %}
- {% if is_granted('ROLE_JURY') or (current_team_contest is not null and current_team_contest.freezeData.started) %}
-
-
- Submit
-
-
- {% else %}
-
-
- Submit
-
-
- {% endif %}
+ {% if stats is defined %}
+
+ {% for correct in [true, false] %}
+
+ {% for bucket in 0..stats.numBuckets - 1 %}
+ {% if correct %}
+ {% set index = 'correct' %}
+ {% set maxBucketSize = stats.maxBucketSizeCorrect %}
+ {% else %}
+ {% set index = 'incorrect' %}
+ {% set maxBucketSize = stats.maxBucketSizeIncorrect %}
+ {% endif %}
+ {% set stat = stats.problems[problem.problem.probid][index][bucket] %}
+ {% set count = stat.count %}
+ {% if maxBucketSize == 0 %}
+ {% set bucket = 0 %}
+ {% else %}
+ {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
+ {% endif %}
+ {% if count == 1 %}
+ {% set submissionText = 'submission' %}
+ {% else %}
+ {% set submissionText = 'submissions' %}
+ {% endif %}
+ {% if not contest.freezeData.showFinal and contest.freezetime and stat.end.timestamp >= contest.freezetime %}
+ {% set maxBucketSize = max(1, stats.maxBucketSizeCorrect, stats.maxBucketSizeIncorrect) %}
+ {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
+ {% set itemClass = 'frozen' ~ '-' ~ bucket %}
+ {% set label = count ~ ' ' ~ submissionText ~ ' in freeze' %}
+ {% else %}
+ {% set itemClass = index ~ '-' ~ bucket %}
+ {% set label = count ~ ' ' ~ index ~ ' ' ~ submissionText %}
+ {% endif %}
+
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
{% endif %}
- {% set clarificationsCount = 0 %}
- {% set unseenClarificationCount = 0 %}
- {% if clarifications[problem.probid] is defined %}
- {% set clarificationsCount = clarifications[problem.probid] | length %}
- {% for clar in clarifications[problem.probid] %}
- {% if team and team.unreadClarifications.contains(clar) %}
- {% set unseenClarificationCount = unseenClarificationCount + 1 %}
+
-
- {% if problem.problem.attachments | length > 0 %}
-
-
- {% for attachment in problem.problem.attachments %}
- -
-
- {{ attachment.name }}
-
-
- {% endfor %}
-
- {% endif %}
-
-
-
- {% endfor %}
-
+
+ {% endfor %}
+
- {% if showLimits and timeFactorDiffers %}
-
-
-
- * language time factors apply
+ {% if showLimits and timeFactorDiffers %}
+
+
+
+ * language time factors apply
+
-
- {% endif %}
-
-{% endif %}
+ {% endif %}
+
+ {% endif %}
+{% endfor %}
From 81646ba5e00259c77ca5184c6f67d3edea2c61d0 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Mon, 10 Nov 2025 21:05:46 +0100
Subject: [PATCH 2/5] Alternative solution by collapsing cards
When you press the button it displays the card of the solved problem.
---
webapp/public/index.php | 0
webapp/src/Service/DOMJudgeService.php | 5 +
.../templates/partials/problem_list.html.twig | 239 ++++--------------
.../partials/problem_list_cards.html.twig | 168 ++++++++++++
4 files changed, 217 insertions(+), 195 deletions(-)
mode change 100644 => 100755 webapp/public/index.php
create mode 100644 webapp/templates/partials/problem_list_cards.html.twig
diff --git a/webapp/public/index.php b/webapp/public/index.php
old mode 100644
new mode 100755
diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php
index c23163ad2e..baf43e7968 100644
--- a/webapp/src/Service/DOMJudgeService.php
+++ b/webapp/src/Service/DOMJudgeService.php
@@ -1096,12 +1096,15 @@ public function getTwigDataForProblemsAction(
}
$allProblems = [null => [], 'solved' => []];
+ $solvedProblem = [];
if ($cache) {
foreach ($cache as $ind => $cachedProblem) {
if ($cachedProblem->getIsCorrect(true)) {
$allProblems['solved'][] = $problems[$ind];
+ $solvedProblem[] = true;
} else {
$allProblems[null][] = $problems[$ind];
+ $solvedProblem[] = false;
}
}
} else {
@@ -1109,6 +1112,8 @@ public function getTwigDataForProblemsAction(
}
$data = [
'allproblems' => $allProblems,
+ 'fullallproblems' => [null => $problems],
+ 'solved' => $solvedProblem,
'samples' => $samples,
'showLimits' => $showLimits,
'defaultMemoryLimit' => $defaultMemoryLimit,
diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig
index 8a211cf1c1..3effa04a04 100644
--- a/webapp/templates/partials/problem_list.html.twig
+++ b/webapp/templates/partials/problem_list.html.twig
@@ -16,206 +16,55 @@
problemset
{% endif %}
-
-
-{% for identifier, problems in allproblems %}
- {% if problems is empty %}
- {% if not identifier %}
-
No problem texts available at this point.
- {% endif %}
- {% else %}
-
- {% if show_jury_warning is defined and show_jury_warning %}
-
- This is a preview of how the page will look like for teams and the public after the contest has started.
-
- {% endif %}
-
- {% if identifier %}
-
- Already solved problems
-
-
- {% endif%}
-
-
- {% for problem in problems %}
-
- {% set numsamples = samples[problem.probid] %}
- {% if problem.problem.interactiveProblem %}
- {% set numsamples = 0 %}
- {% endif %}
-
-
-
- {{ problem | problemBadge }}
-
-
- {{ problem.problem.name }}
-
- {% if showLimits %}
-
- Limits: {{ problem.problem.timelimit }}
- second
- {%- if problem.problem.timelimit > 1 %}s{% endif %}
- {%- if timeFactorDiffers -%}
- *
- {% endif %}
- /
- {{ ((problem.problem.memlimit | default(defaultMemoryLimit)) * 1024) | printSize }}
-
- {% endif %}
- {% if problem.problem.languages | length != 0 %}
-
- Language{% if problem.problem.languages | length > 1 %}s{% endif %}:
- {% for lang in problem.problem.languages %}
- {{ lang.name }}{% if not loop.last %}, {% endif %}
- {% endfor %}
-
- {% endif %}
-
- Type: {{ problem.problem.typesAsString }}
-
- {% if stats is defined %}
-
- {% for correct in [true, false] %}
-
- {% for bucket in 0..stats.numBuckets - 1 %}
- {% if correct %}
- {% set index = 'correct' %}
- {% set maxBucketSize = stats.maxBucketSizeCorrect %}
- {% else %}
- {% set index = 'incorrect' %}
- {% set maxBucketSize = stats.maxBucketSizeIncorrect %}
- {% endif %}
- {% set stat = stats.problems[problem.problem.probid][index][bucket] %}
- {% set count = stat.count %}
- {% if maxBucketSize == 0 %}
- {% set bucket = 0 %}
- {% else %}
- {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
- {% endif %}
- {% if count == 1 %}
- {% set submissionText = 'submission' %}
- {% else %}
- {% set submissionText = 'submissions' %}
- {% endif %}
- {% if not contest.freezeData.showFinal and contest.freezetime and stat.end.timestamp >= contest.freezetime %}
- {% set maxBucketSize = max(1, stats.maxBucketSizeCorrect, stats.maxBucketSizeIncorrect) %}
- {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
- {% set itemClass = 'frozen' ~ '-' ~ bucket %}
- {% set label = count ~ ' ' ~ submissionText ~ ' in freeze' %}
- {% else %}
- {% set itemClass = index ~ '-' ~ bucket %}
- {% set label = count ~ ' ' ~ index ~ ' ' ~ submissionText %}
- {% endif %}
-
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
- {% endif %}
-
-
- {% if show_submit_button | default(false) %}
- {% if is_granted('ROLE_JURY') or (current_team_contest is not null and current_team_contest.freezeData.started) %}
-
-
- Submit
-
-
- {% else %}
-
-
- Submit
-
-
- {% endif %}
- {% endif %}
-
- {% set clarificationsCount = 0 %}
- {% set unseenClarificationCount = 0 %}
- {% if clarifications[problem.probid] is defined %}
- {% set clarificationsCount = clarifications[problem.probid] | length %}
- {% for clar in clarifications[problem.probid] %}
- {% if team and team.unreadClarifications.contains(clar) %}
- {% set unseenClarificationCount = unseenClarificationCount + 1 %}
- {% endif %}
- {% endfor %}
- {% endif %}
- {% if clarificationsCount > 0 %}
-
-
- {% if clarificationsCount > 0 %}
- {% set badgeClass = 'text-bg-info' %}
- {% if unseenClarificationCount > 0 %}
- {% set badgeClass = 'text-bg-danger' %}
- {% endif %}
- {{ clarificationsCount }}
- {% endif %}
- clarifications
-
- {% endif %}
-
-
-
+ {% if allproblems['solved'] is defined and allproblems['solved'] is not empty %}
+
+
+ show solved problems
+
+ {% endif %}
+
- {% if problem.problem.problemstatementType is not empty %}
-
-
- statement
-
- {% endif %}
+{% set found_problem_texts = false %}
+{% for problems in allproblems %}
+ {% if problems is not empty %}
+ {% set found_problem_texts = true %}
+ {% endif %}
+{% endfor %}
- {% if numsamples > 0 %}
-
- samples
-
- {% endif %}
-
+{% if not found_problem_texts %}
+
No problem texts available at this point.
+{% else %}
+
+ {% if show_jury_warning is defined and show_jury_warning %}
+
+ This is a preview of how the page will look like for teams and the public after the contest has started.
+
+ {% endif %}
- {% if problem.problem.attachments | length > 0 %}
-
-
- {% for attachment in problem.problem.attachments %}
- -
-
- {{ attachment.name }}
-
-
- {% endfor %}
-
- {% endif %}
+
Alternative 1: Enable/Disable with button
+ {% include 'partials/problem_list_cards.html.twig' with {'problems': fullallproblems[null], 'collapse': 2} %}
+
+
Alternative 2: Those would be there by default
+ {% include 'partials/problem_list_cards.html.twig' with {'problems': allproblems[null], 'collapse': 0} %}
-
-
-
- {% endfor %}
-
+ {% if allproblems['solved'] is defined %}
+
+ Alternative 2: Already solved problems
+
+
+ {% include 'partials/problem_list_cards.html.twig' with {'problems': allproblems['solved'], 'collapse': 1} %}
+ {% endif %}
- {% if showLimits and timeFactorDiffers %}
-
-
-
- * language time factors apply
-
+ {% if showLimits and timeFactorDiffers %}
+
+
+
+ * language time factors apply
- {% endif %}
-
- {% endif %}
-{% endfor %}
+
+ {% endif %}
+
+{% endif %}
diff --git a/webapp/templates/partials/problem_list_cards.html.twig b/webapp/templates/partials/problem_list_cards.html.twig
new file mode 100644
index 0000000000..7baa3a55e0
--- /dev/null
+++ b/webapp/templates/partials/problem_list_cards.html.twig
@@ -0,0 +1,168 @@
+
+ {% for ind, problem in problems %}
+
+ {% set numsamples = samples[problem.probid] %}
+ {% if problem.problem.interactiveProblem %}
+ {% set numsamples = 0 %}
+ {% endif %}
+
+
+
+ {{ problem | problemBadge }}
+
+
+ {{ problem.problem.name }}
+
+ {% if showLimits %}
+
+ Limits: {{ problem.problem.timelimit }}
+ second
+ {%- if problem.problem.timelimit > 1 %}s{% endif %}
+ {%- if timeFactorDiffers -%}
+ *
+ {% endif %}
+ /
+ {{ ((problem.problem.memlimit | default(defaultMemoryLimit)) * 1024) | printSize }}
+
+ {% endif %}
+ {% if problem.problem.languages | length != 0 %}
+
+ Language{% if problem.problem.languages | length > 1 %}s{% endif %}:
+ {% for lang in problem.problem.languages %}
+ {{ lang.name }}{% if not loop.last %}, {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+ Type: {{ problem.problem.typesAsString }}
+
+
+ {% if stats is defined %}
+
+ {% for correct in [true, false] %}
+
+ {% for bucket in 0..stats.numBuckets - 1 %}
+ {% if correct %}
+ {% set index = 'correct' %}
+ {% set maxBucketSize = stats.maxBucketSizeCorrect %}
+ {% else %}
+ {% set index = 'incorrect' %}
+ {% set maxBucketSize = stats.maxBucketSizeIncorrect %}
+ {% endif %}
+ {% set stat = stats.problems[problem.problem.probid][index][bucket] %}
+ {% set count = stat.count %}
+ {% if maxBucketSize == 0 %}
+ {% set bucket = 0 %}
+ {% else %}
+ {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
+ {% endif %}
+ {% if count == 1 %}
+ {% set submissionText = 'submission' %}
+ {% else %}
+ {% set submissionText = 'submissions' %}
+ {% endif %}
+ {% if not contest.freezeData.showFinal and contest.freezetime and stat.end.timestamp >= contest.freezetime %}
+ {% set maxBucketSize = max(1, stats.maxBucketSizeCorrect, stats.maxBucketSizeIncorrect) %}
+ {% set bucket = (count / maxBucketSize * 9) | round(0, 'ceil') %}
+ {% set itemClass = 'frozen' ~ '-' ~ bucket %}
+ {% set label = count ~ ' ' ~ submissionText ~ ' in freeze' %}
+ {% else %}
+ {% set itemClass = index ~ '-' ~ bucket %}
+ {% set label = count ~ ' ' ~ index ~ ' ' ~ submissionText %}
+ {% endif %}
+
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+ {% endif %}
+
+
+ {% if show_submit_button | default(false) %}
+ {% if is_granted('ROLE_JURY') or (current_team_contest is not null and current_team_contest.freezeData.started) %}
+
+
+ Submit
+
+
+ {% else %}
+
+
+ Submit
+
+
+ {% endif %}
+ {% endif %}
+
+ {% set clarificationsCount = 0 %}
+ {% set unseenClarificationCount = 0 %}
+ {% if clarifications[problem.probid] is defined %}
+ {% set clarificationsCount = clarifications[problem.probid] | length %}
+ {% for clar in clarifications[problem.probid] %}
+ {% if team and team.unreadClarifications.contains(clar) %}
+ {% set unseenClarificationCount = unseenClarificationCount + 1 %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if clarificationsCount > 0 %}
+
+
+ {% if clarificationsCount > 0 %}
+ {% set badgeClass = 'text-bg-info' %}
+ {% if unseenClarificationCount > 0 %}
+ {% set badgeClass = 'text-bg-danger' %}
+ {% endif %}
+ {{ clarificationsCount }}
+ {% endif %}
+ clarifications
+
+ {% endif %}
+
+
+
+
+ {% if problem.problem.problemstatementType is not empty %}
+
+
+ statement
+
+ {% endif %}
+
+ {% if numsamples > 0 %}
+
+ samples
+
+ {% endif %}
+
+
+ {% if problem.problem.attachments | length > 0 %}
+
+
+ {% for attachment in problem.problem.attachments %}
+ -
+
+ {{ attachment.name }}
+
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+ {% endfor %}
+
From 53b21f450c37055c13f81a6ffae10e3fe5f0b9d4 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Wed, 12 Nov 2025 08:50:26 +0100
Subject: [PATCH 3/5] Fix first PHPStan error
---
webapp/src/Service/DOMJudgeService.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php
index baf43e7968..42b401e56d 100644
--- a/webapp/src/Service/DOMJudgeService.php
+++ b/webapp/src/Service/DOMJudgeService.php
@@ -24,6 +24,7 @@
use App\Entity\ProblemAttachment;
use App\Entity\QueueTask;
use App\Entity\Rejudging;
+use App\Entity\ScoreCache;
use App\Entity\Submission;
use App\Entity\Team;
use App\Entity\TeamAffiliation;
From 99ca6f27fd22b21db548fc2de1a687809220f321 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Wed, 12 Nov 2025 08:55:24 +0100
Subject: [PATCH 4/5] Fix 2nd PHPStan error
---
webapp/src/Service/DOMJudgeService.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php
index 42b401e56d..740eb189d8 100644
--- a/webapp/src/Service/DOMJudgeService.php
+++ b/webapp/src/Service/DOMJudgeService.php
@@ -1011,8 +1011,8 @@ public function getContestStats(Contest $contest): ContestStatus
/**
* @param ScoreCache[]|null $cache
- * @return array{'problems': ContestProblem[], 'samples': string[], 'showLimits': bool,
- * 'defaultMemoryLimit': int, 'timeFactorDiffers': bool,
+ * @return array{'allproblems':
, 'samples': string[], 'showLimits': bool,
+ * 'defaultMemoryLimit': int, 'timeFactorDiffers': bool, 'solved': bool[], fullallproblems: array,
* 'stats': array{'numBuckets': int, 'maxBucketSizeCorrect': int,
* 'maxBucketSizeCorrect': int, 'maxBucketSizeIncorrect': int,
* 'problems': array,
From b5a1d3cf013b9815f014dd212878ca5a3f90f793 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Wed, 12 Nov 2025 09:04:42 +0100
Subject: [PATCH 5/5] Fix quoting
---
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 740eb189d8..587c94cf69 100644
--- a/webapp/src/Service/DOMJudgeService.php
+++ b/webapp/src/Service/DOMJudgeService.php
@@ -1012,7 +1012,7 @@ public function getContestStats(Contest $contest): ContestStatus
/**
* @param ScoreCache[]|null $cache
* @return array{'allproblems': , 'samples': string[], 'showLimits': bool,
- * 'defaultMemoryLimit': int, 'timeFactorDiffers': bool, 'solved': bool[], fullallproblems: array,
+ * 'defaultMemoryLimit': int, 'timeFactorDiffers': bool, 'solved': bool[], 'fullallproblems': array,
* 'stats': array{'numBuckets': int, 'maxBucketSizeCorrect': int,
* 'maxBucketSizeCorrect': int, 'maxBucketSizeIncorrect': int,
* 'problems': array,