From 00712341f614e99e0ce2335f1ed23c4d4a77e5a2 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 14:40:45 +0100
Subject: [PATCH 1/7] Warn jury for clarifications before contest start
There are good reasons to already send the clarification, so we don't disable
the feature but warn the user.
---
.../Jury/ClarificationController.php | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/webapp/src/Controller/Jury/ClarificationController.php b/webapp/src/Controller/Jury/ClarificationController.php
index 4eda5224e2..e8a9711095 100644
--- a/webapp/src/Controller/Jury/ClarificationController.php
+++ b/webapp/src/Controller/Jury/ClarificationController.php
@@ -34,6 +34,22 @@ public function __construct(
protected readonly EventLogService $eventLogService
) {}
+ private function warnClarificationBeforeContestStart(): void
+ {
+ $cc = $this->dj->getCurrentContest();
+ $message = "Generic clarifications are visible before contest start.";
+ if ($cc && $cc->getStartTime() > Utils::now()) {
+ $this->addFlash('warning', $message);
+ } elseif (!$cc) {
+ foreach ($this->dj->getCurrentContests() as $cc) {
+ if ($cc->getStartTime() > Utils::now()) {
+ $this->addFlash('warning', $message);
+ return;
+ }
+ }
+ }
+ }
+
#[Route(path: '', name: 'jury_clarifications')]
public function indexAction(
#[MapQueryParameter(name: 'filter')]
@@ -41,6 +57,7 @@ public function indexAction(
#[MapQueryParameter(name: 'queue')]
string $currentQueue = 'all',
): Response {
+ $this->warnClarificationBeforeContestStart();
$categories = $this->config->get('clar_categories');
if ($contest = $this->dj->getCurrentContest()) {
$contestIds = [$contest->getCid()];
@@ -116,6 +133,7 @@ public function indexAction(
#[Route(path: '/{id<\d+>}', name: 'jury_clarification')]
public function viewAction(Request $request, int $id): Response
{
+ $this->warnClarificationBeforeContestStart();
$clarification = $this->em->getRepository(Clarification::class)->find($id);
if (!$clarification) {
throw new NotFoundHttpException(sprintf('Clarification with ID %s not found', $id));
@@ -239,6 +257,7 @@ public function composeClarificationAction(
#[MapQueryParameter]
?string $teamto = null,
): Response {
+ $this->warnClarificationBeforeContestStart();
$formData = ['recipient' => JuryClarificationType::RECIPIENT_MUST_SELECT];
if ($teamto !== null) {
From d54c96e74a5c0826b00a0f32e317a937251a3cfa Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 16:14:15 +0100
Subject: [PATCH 2/7] Prevent clarifications before the contest without
explicit time
In the Jury UI we can warn with a flash message, for API we can't easily
prevent automated tools from unintentionally disclosing the information.
We provide both relevant times, either contest start or now to keep this easy
to copy/paste. The format is already in the timezone of the contest to make
sure validation passes.
In theory this can still give problems in case you submit a clarification and
afterwards the contest starts later, it's up to the contest admin to change the
database in such cases as we don't have a reltime for clarifications.
---
.../src/Controller/API/ClarificationController.php | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/webapp/src/Controller/API/ClarificationController.php b/webapp/src/Controller/API/ClarificationController.php
index 397e2e499b..93b641c509 100644
--- a/webapp/src/Controller/API/ClarificationController.php
+++ b/webapp/src/Controller/API/ClarificationController.php
@@ -8,6 +8,7 @@
use App\Entity\ContestProblem;
use App\Entity\Team;
use App\Utils\Utils;
+use DateTime;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\QueryBuilder;
use Exception;
@@ -208,6 +209,17 @@ public function addAction(
} else {
throw new BadRequestHttpException('A team can not assign time.');
}
+ } elseif ($this->isGranted('ROLE_API_WRITER') && $contest->getStartTime() > $time) {
+ $startTime = $contest->getStarttimeString();
+ $startTimeTimeZone = DateTime::createFromFormat('Y-m-d h:i:s e', $startTime)->getTimezone();
+ $now = DateTime::createFromFormat('U.u', (string) $time);
+ $now->setTimezone($startTimeTimeZone); // We can't the timezone in createFromFormat as it always picks UTC.
+ throw new BadRequestHttpException(
+ "Sending a clarification before the contest can disclose restricted information, "
+ . "provide an explicit time when this clarification should be visible. "
+ . "For the start of this contest: " . $contest->getStarttimeString() . ", "
+ . "for the current time: " . $now->format('Y-m-d H:i:s e') . "."
+ );
}
$clarification->setSubmittime($time);
From 4706e7536a4a96db7e59d6a55e82c7e122fd88df Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 16:47:49 +0100
Subject: [PATCH 3/7] Display pre-contest clarifications
We already disclose those in the API, so keep this consistent. This
does result in the `contest time` of that clarification to be negative
but thats factually true.
---
webapp/templates/team/partials/index_content.html.twig | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/webapp/templates/team/partials/index_content.html.twig b/webapp/templates/team/partials/index_content.html.twig
index 34c7ca453c..06e8061eb8 100644
--- a/webapp/templates/team/partials/index_content.html.twig
+++ b/webapp/templates/team/partials/index_content.html.twig
@@ -6,6 +6,10 @@
From 1f77c9af38ea210ad5d5a8914b57b8fe08af3619 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 18:16:52 +0100
Subject: [PATCH 4/7] Allow teams to request clarifications before the contest
We allow via the API, this just makes the behaviour uniform.
---
.../partials/clarifications_team.html.twig | 22 +++++++++++++++
.../team/partials/index_content.html.twig | 27 +++----------------
2 files changed, 26 insertions(+), 23 deletions(-)
create mode 100644 webapp/templates/team/partials/clarifications_team.html.twig
diff --git a/webapp/templates/team/partials/clarifications_team.html.twig b/webapp/templates/team/partials/clarifications_team.html.twig
new file mode 100644
index 0000000000..6b805b6c38
--- /dev/null
+++ b/webapp/templates/team/partials/clarifications_team.html.twig
@@ -0,0 +1,22 @@
+{% if clarifications is not empty or always_display %}
+
Clarifications
+ {% if clarifications is empty %}
+
No clarifications.
+ {% else %}
+ {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications} %}
+ {% endif %}
+{% endif %}
+
+
Clarification Requests
+{% if clarificationRequests is empty %}
+
No clarification request.
+{% else %}
+ {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarificationRequests} %}
+{% endif %}
+
+
+
diff --git a/webapp/templates/team/partials/index_content.html.twig b/webapp/templates/team/partials/index_content.html.twig
index 06e8061eb8..eb65ca2820 100644
--- a/webapp/templates/team/partials/index_content.html.twig
+++ b/webapp/templates/team/partials/index_content.html.twig
@@ -6,10 +6,8 @@
Contest {{ contest | printContestStart }}
- {% if clarifications is not empty %}
-
Clarifications
- {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications} %}
- {% endif %}
+
+ {% include 'team/partials/clarifications_team.html.twig' with {'always_display': false} %}
{% else %}
@@ -30,26 +28,9 @@
{% include 'team/partials/submission_list.html.twig' %}
-
-
Clarifications
- {% if clarifications is empty %}
-
No clarifications.
- {% else %}
- {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications} %}
- {% endif %}
-
Clarification Requests
- {% if clarificationRequests is empty %}
-
No clarification request.
- {% else %}
- {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarificationRequests} %}
- {% endif %}
-
-
+
+ {% include 'team/partials/clarifications_team.html.twig' with {'always_display': true} %}
{% endif %}
From 8342cf2904fc5b51f21e7136a3d34ef725d65a8a Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 16:47:16 +0100
Subject: [PATCH 5/7] Don't display clarification time from before contest
It looks ugly in some cases and less professional in others where printing
happened much earlier. The exact time is not relevant and could lead to
discussions with teams.
---
webapp/src/Twig/TwigExtension.php | 5 ++++-
webapp/templates/team/partials/clarification_list.html.twig | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php
index 60df1de6cc..8fd2d2428d 100644
--- a/webapp/src/Twig/TwigExtension.php
+++ b/webapp/src/Twig/TwigExtension.php
@@ -205,13 +205,16 @@ public function printelapsedminutes(float $start, float $end): string
* Print a time formatted as specified. The format is according to date().
* @param Contest|null $contest If given, print time relative to that contest start.
*/
- public function printtime(string|float|null $datetime, ?string $format = null, ?Contest $contest = null): string
+ public function printtime(string|float|null $datetime, ?string $format = null, ?Contest $contest = null, bool $squash = false): string
{
if ($datetime === null) {
$datetime = Utils::now();
}
if ($contest !== null && $this->config->get('show_relative_time')) {
$relativeTime = $contest->getContestTime((float)$datetime);
+ if ($relativeTime < 0 && $squash) {
+ return "Before contest";
+ }
$sign = ($relativeTime < 0 ? -1 : 1);
$relativeTime *= $sign;
// We're not showing seconds, while the last minute before
diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig
index daa60a8b53..c2f3169d13 100644
--- a/webapp/templates/team/partials/clarification_list.html.twig
+++ b/webapp/templates/team/partials/clarification_list.html.twig
@@ -23,7 +23,7 @@
|
- {{ clarification.submittime | printtime(null, clarification.contest) }}
+ {{ clarification.submittime | printtime(null, clarification.contest, true) }}
|
{%- if clarification.sender is null %}
From c8c97ec847170c8f91fd0eef2045aa845c0b5405 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 16:40:01 +0100
Subject: [PATCH 6/7] Don't reference undisclosed problems in clarifications
before conteststart
---
webapp/src/Controller/API/ClarificationController.php | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/webapp/src/Controller/API/ClarificationController.php b/webapp/src/Controller/API/ClarificationController.php
index 93b641c509..d0c82ca002 100644
--- a/webapp/src/Controller/API/ClarificationController.php
+++ b/webapp/src/Controller/API/ClarificationController.php
@@ -309,6 +309,14 @@ protected function getQueryBuilder(Request $request): QueryBuilder
}
}
+ // For non-API-reader users, only expose the problems after the contest has started.
+ // `WF Access Policy` allows for clarifications before the contest, but not to disclose the problem
+ // so referencing them in clarifications would violate referential integrity.
+ if (!$this->dj->checkrole('api_reader')) {
+ $queryBuilder->andWhere('c.starttime < :now OR clar.problem IS NULL')
+ ->setParameter('now', Utils::now());
+ }
+
if ($request->query->has('problem')) {
$queryBuilder
->andWhere('clar.problem = :problem')
From 3a60f88448876f82d9b0ca77e901b648c6cda550 Mon Sep 17 00:00:00 2001
From: Michael Vasseur <14887731+vmcj@users.noreply.github.com>
Date: Sun, 26 Oct 2025 16:45:44 +0100
Subject: [PATCH 7/7] Don't display clarifications about non-disclosed problems
yet
We do for the jury so they can still see the interface as a time would see it.
---
webapp/src/Controller/Team/MiscController.php | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/webapp/src/Controller/Team/MiscController.php b/webapp/src/Controller/Team/MiscController.php
index 1ad3d4e7a1..d304264ad7 100644
--- a/webapp/src/Controller/Team/MiscController.php
+++ b/webapp/src/Controller/Team/MiscController.php
@@ -5,13 +5,13 @@
use App\Controller\BaseController;
use App\DataTransferObject\SubmissionRestriction;
use App\Entity\Clarification;
-use App\Entity\Language;
use App\Form\Type\PrintType;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use App\Service\EventLogService;
use App\Service\ScoreboardService;
use App\Service\SubmissionService;
+use App\Utils\Utils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
@@ -92,8 +92,7 @@ public function homeAction(Request $request): Response
paginated: false
)[0];
- /** @var Clarification[] $clarifications */
- $clarifications = $this->em->createQueryBuilder()
+ $qb = $this->em->createQueryBuilder()
->from(Clarification::class, 'c')
->leftJoin('c.problem', 'p')
->leftJoin('c.sender', 's')
@@ -105,9 +104,12 @@ public function homeAction(Request $request): Response
->setParameter('contest', $contest)
->setParameter('team', $team)
->addOrderBy('c.submittime', 'DESC')
- ->addOrderBy('c.clarid', 'DESC')
- ->getQuery()
- ->getResult();
+ ->addOrderBy('c.clarid', 'DESC');
+ if (!$this->dj->checkrole('jury') && $contest->getStartTimeObject()->getTimestamp() > time()) {
+ $qb->andWhere('c.problem IS NULL');
+ }
+ /** @var Clarification[] $clarifications */
+ $clarifications = $qb->getQuery()->getResult();
/** @var Clarification[] $clarificationRequests */
$clarificationRequests = $this->em->createQueryBuilder()