diff --git a/webapp/src/Controller/API/ClarificationController.php b/webapp/src/Controller/API/ClarificationController.php index 397e2e499b..d0c82ca002 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); @@ -297,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') 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) { 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() 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 %} 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 %} + +
+ + Request clarification + +
+ diff --git a/webapp/templates/team/partials/index_content.html.twig b/webapp/templates/team/partials/index_content.html.twig index 34c7ca453c..eb65ca2820 100644 --- a/webapp/templates/team/partials/index_content.html.twig +++ b/webapp/templates/team/partials/index_content.html.twig @@ -6,6 +6,8 @@

Contest {{ contest | printContestStart }}

+ + {% include 'team/partials/clarifications_team.html.twig' with {'always_display': false} %} {% else %}
@@ -26,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 %} - -
- - Request clarification - -
+
+ {% include 'team/partials/clarifications_team.html.twig' with {'always_display': true} %}
{% endif %}