diff --git a/webapp/src/Controller/Team/ClarificationController.php b/webapp/src/Controller/Team/ClarificationController.php index 8134a4e2bd..6ed0260684 100644 --- a/webapp/src/Controller/Team/ClarificationController.php +++ b/webapp/src/Controller/Team/ClarificationController.php @@ -45,6 +45,61 @@ public function __construct( parent::__construct($em, $eventLogService, $dj, $kernel); } + #[Route(path: '/clarifications/by-problem/{probId<\d+>}', name: 'team_clarification_by_prob')] + public function viewByProblemAction(Request $request, int $probId): Response + { + $user = $this->dj->getUser(); + $team = $user->getTeam(); + $teamId = $team->getTeamid(); + $contest = $this->dj->getCurrentContest($teamId); + + $problem = $this->em->getRepository(Problem::class)->find($probId); + if ($problem === null) { + throw new NotFoundHttpException(sprintf('Problem %d not found', $probId)); + } + $contestProblem = $problem->getContestProblems(); + $foundProblemInContest = false; + foreach ($contestProblem as $cp) { + if ($cp->getContest()->getCid() === $contest->getCid()) { + $foundProblemInContest = true; + break; + } + } + if (!$foundProblemInContest) { + throw new NotFoundHttpException(sprintf('Problem %d not in current contest', $probId)); + } + + /** @var Clarification[] $clarifications */ + $clarifications = $this->em->createQueryBuilder() + ->from(Clarification::class, 'c') + ->leftJoin('c.problem', 'p') + ->leftJoin('c.sender', 's') + ->leftJoin('c.recipient', 'r') + ->select('c', 'p') + ->andWhere('c.contest = :contest') + ->andWhere('c.sender IS NULL') + ->andWhere('c.recipient = :team OR c.recipient IS NULL') + ->andWhere('c.problem = :problem') + ->setParameter('contest', $contest) + ->setParameter('team', $team) + ->setParameter('problem', $problem) + ->addOrderBy('c.submittime', 'DESC') + ->addOrderBy('c.clarid', 'DESC') + ->getQuery() + ->getResult(); + + $data = [ + 'clarifications' => $clarifications, + 'team' => $team, + 'problem' => $problem, + ]; + if ($request->isXmlHttpRequest()) { + return $this->render('team/clarifications_by_problem_modal.html.twig', $data); + } else { + return $this->render('team/clarifications_by_problem.html.twig', $data); + } + } + /** * @throws NonUniqueResultException */ diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 1603e6dd0b..74f699ffba 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -1028,6 +1028,7 @@ public function getTwigDataForProblemsAction( $problems = []; $samples = []; + $clars = []; if ($contest && ($forJury || $contest->getFreezeData()->started())) { $problems = $this->em->createQueryBuilder() ->from(ContestProblem::class, 'cp') @@ -1057,6 +1058,27 @@ public function getTwigDataForProblemsAction( foreach ($samplesData as $sample) { $samples[$sample['probid']] = $sample['numsamples']; } + + $raw_clars = $this->em->createQueryBuilder() + ->from(Clarification::class, 'clar') + ->select('clar') + ->andWhere('clar.contest = :cid') + // Only clars associated with a problem. + ->andWhere('clar.problem IS NOT NULL') + // Only clars send from the jury. + ->andWhere('clar.sender IS NULL') + // Only clars send to all teams or just this team. + ->andWhere('clar.recipient IS NULL OR clar.recipient = :teamid') + ->setParameter('cid', $contest->getCid()) + ->setParameter('teamid', $teamId) + ->orderBy('clar.submittime', 'DESC') + ->getQuery() + ->getResult(); + + // Group clarifications by problem id. + foreach ($raw_clars as $clar) { + $clars[$clar->getProblem()->getProbid()][] = $clar; + } } $data = [ @@ -1065,6 +1087,8 @@ public function getTwigDataForProblemsAction( 'showLimits' => $showLimits, 'defaultMemoryLimit' => $defaultMemoryLimit, 'timeFactorDiffers' => $timeFactorDiffers, + 'clarifications' => $clars, + 'team' => $teamId ? $this->em->getRepository(Team::class)->find($teamId) : null, ]; if ($contest && $this->config->get('show_public_stats')) { diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig index 6d7a3f0298..8414498602 100644 --- a/webapp/templates/partials/problem_list.html.twig +++ b/webapp/templates/partials/problem_list.html.twig @@ -109,21 +109,6 @@ {% endif %}
- {% if problem.problem.problemstatementType is not empty %} - - - statement - - {% endif %} - - {% if numsamples > 0 %} - - samples - - {% 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) %} @@ -139,6 +124,49 @@ {% 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.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 %} diff --git a/webapp/templates/team/clarifications_by_problem.html.twig b/webapp/templates/team/clarifications_by_problem.html.twig new file mode 100644 index 0000000000..2a81b1330d --- /dev/null +++ b/webapp/templates/team/clarifications_by_problem.html.twig @@ -0,0 +1,33 @@ +{% extends 'team/base.html.twig' %} + +{% block title %}View clarifications for problem {{ problem.name }}{% endblock %} + +{% block extrahead %} + {{ parent() }} + +{% endblock %} + +{% block content %} +

Clarifications for {{ problem | problemBadgeForContest }} {{ problem.name }}

+ + {% if clarifications is empty %} +

No clarifications.

+ {% else %} + {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications} %} + {% endif %} +{% endblock %} diff --git a/webapp/templates/team/clarifications_by_problem_modal.html.twig b/webapp/templates/team/clarifications_by_problem_modal.html.twig new file mode 100644 index 0000000000..a48d09d1aa --- /dev/null +++ b/webapp/templates/team/clarifications_by_problem_modal.html.twig @@ -0,0 +1,31 @@ +{% extends "partials/modal.html.twig" %} + +{% block title %}View clarifications for problem {{ problem | problemBadgeForContest }} {{ problem.name }}{% endblock %} + +{% block extrahead %} + {{ parent() }} + +{% endblock %} + +{% block content %} + {% if clarifications is empty %} +

No clarifications.

+ {% else %} + {% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications, subject: false} %} + {% endif %} +{% endblock %} diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig index 47b154c542..1095704126 100644 --- a/webapp/templates/team/partials/clarification_list.html.twig +++ b/webapp/templates/team/partials/clarification_list.html.twig @@ -1,3 +1,8 @@ +{% set includeSubject = true %} +{% if subject is defined and not subject %} + {% set includeSubject = false %} +{% endif %} +
@@ -5,7 +10,9 @@ - + {% if includeSubject %} + + {% endif %} @@ -41,17 +48,19 @@ {{ recipient | u.truncate(teamname_max_length, '…') }} - + {% if includeSubject %} + + {% endif %}
time from tosubjectsubjecttext
- - {%- if clarification.problem -%} - problem {{ clarification.contestProblem | problemBadge -}} - {%- elseif clarification.category -%} - {{- categories[clarification.category]|default('general') -}} - {%- else -%} - general - {%- endif -%} - - + + {%- if clarification.problem -%} + problem {{ clarification.contestProblem | problemBadge -}} + {%- elseif clarification.category -%} + {{- categories[clarification.category]|default('general') -}} + {%- else -%} + general + {%- endif -%} + + diff --git a/webapp/templates/team/problems.html.twig b/webapp/templates/team/problems.html.twig index 9ceb23397d..2b7fe77849 100644 --- a/webapp/templates/team/problems.html.twig +++ b/webapp/templates/team/problems.html.twig @@ -10,6 +10,7 @@ problem_statement_path: 'team_problem_statement', problem_attachment_path: 'team_problem_attachment', problem_sample_zip_path: 'team_problem_sample_zip', - show_submit_button: true + show_submit_button: true, + team: team } %} {% endblock %} diff --git a/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php b/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php index 57ce216b35..941169fdbe 100644 --- a/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php +++ b/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php @@ -133,7 +133,7 @@ public function testSamples(): void $cardBodies->eq(2)->filter('.list-group .list-group-item')->count()); // Check the link to download all samples. - $link = $cardBodies->eq(1)->filter('a')->eq(1); + $link = $cardBodies->eq(1)->filter('a')->eq(2); self::assertSame('samples', $link->text(null, true)); self::assertSame(sprintf('/team/%d/samples.zip', $problem->getProbid()),