Skip to content

Commit 1c80b26

Browse files
committed
Warn jury if clarification is claimed concurrently
When the form is rendered, the current jury member assigned to a clarification is stored in a hidden input field. If, during the validation, the currently assigned jury member has changed and is not the current user, warn the jury member and force a resubmission to confirm. Alternative implementation of #2766
1 parent 38179a0 commit 1c80b26

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

webapp/src/Controller/Jury/ClarificationController.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public function viewAction(Request $request, int $id): Response
151151
$lastClarification = end($clarificationList);
152152
$formData['message'] = "> " . str_replace("\n", "\n> ", Utils::wrapUnquoted($lastClarification->getBody())) . "\n\n";
153153

154-
$form = $this->createForm(JuryClarificationType::class, $formData, ['limit_to_team' => $clarification->getSender()]);
154+
$form = $this->createForm(JuryClarificationType::class, $formData, ['limit_to_team' => $clarification->getSender(), 'clarid' => $id]);
155155

156156
$form->handleRequest($request);
157157

@@ -224,6 +224,13 @@ public function viewAction(Request $request, int $id): Response
224224

225225
$parameters['queues'] = $queues;
226226
$parameters['answers'] = $clarificationAnswers;
227+
$parameters['jurymember'] = $this->em->createQueryBuilder()
228+
->select('clar.jury_member')
229+
->from(Clarification::class, 'clar')
230+
->where('clar.clarid = :clarid')
231+
->setParameter('clarid', $id)
232+
->getQuery()
233+
->getSingleResult()['jury_member'];
227234

228235
return $this->render('jury/clarification.html.twig', $parameters);
229236
}

webapp/src/Form/Type/JuryClarificationType.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@
22

33
namespace App\Form\Type;
44

5+
use App\Entity\Clarification;
56
use App\Entity\ContestProblem;
67
use App\Entity\Team;
78
use App\Service\ConfigurationService;
89
use App\Service\DOMJudgeService;
910
use Doctrine\ORM\EntityManagerInterface;
1011
use Symfony\Component\Form\AbstractType;
1112
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
13+
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
1214
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
1315
use Symfony\Component\Form\FormBuilderInterface;
1416
use Symfony\Component\OptionsResolver\OptionsResolver;
1517
use Symfony\Component\Validator\Constraints\NotEqualTo;
18+
use Symfony\Component\Validator\Constraints\Callback;
19+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
1620

1721
class JuryClarificationType extends AbstractType
1822
{
1923
public const RECIPIENT_MUST_SELECT = 'domjudge-must-select';
2024

25+
/** @var int The clarification entity id if the entity exists in the database */
26+
private $clarid;
27+
2128
public function __construct(
2229
private readonly EntityManagerInterface $em,
2330
private readonly ConfigurationService $config,
@@ -26,6 +33,7 @@ public function __construct(
2633

2734
public function buildForm(FormBuilderInterface $builder, array $options): void
2835
{
36+
$this->clarid = $options['clarid'];
2937
$recipientOptions = [
3038
'(select...)' => static::RECIPIENT_MUST_SELECT,
3139
'ALL' => '',
@@ -104,11 +112,18 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
104112
'cols' => 85,
105113
],
106114
]);
115+
116+
$builder->add('jurymember', HiddenType::class, [
117+
'constraints' => [
118+
new Callback([$this, 'checkJuryMember'])
119+
]
120+
]);
107121
}
108122

109123
public function configureOptions(OptionsResolver $resolver): void
110124
{
111125
$resolver->setDefault('limit_to_team', null);
126+
$resolver->setDefault('clarid', null);
112127
}
113128

114129
private function getTeamLabel(Team $team): string
@@ -119,4 +134,25 @@ private function getTeamLabel(Team $team): string
119134

120135
return sprintf('%s (%s)', $team->getEffectiveName(), $team->getExternalId());
121136
}
137+
138+
public function checkJuryMember(mixed $value, ExecutionContextInterface $context, mixed $payload): void
139+
{
140+
if ($this->clarid) {
141+
$juryMember = $this->em->createQueryBuilder()
142+
->select('clar.jury_member')
143+
->from(Clarification::class, 'clar')
144+
->where('clar.clarid = :clarid')
145+
->setParameter('clarid', $this->clarid)
146+
->getQuery()
147+
->getSingleResult()['jury_member'];
148+
149+
// If jury member changed, and we are not currently assigned, warn.
150+
if ($value !== $juryMember && $this->dj->getUser()->getUserIdentifier() !== $juryMember) {
151+
$context->buildViolation("Jury Member '%jury%' claimed this clarification in the meantime.
152+
Please resubmit if you want to continue.")
153+
->setParameter('%jury%', $juryMember)
154+
->addViolation();
155+
}
156+
}
157+
}
122158
}

webapp/templates/jury/partials/clarification_form.html.twig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</div>
1111
</div>
1212
</div>
13+
{{ form_errors(form) }}
1314
<div class="row">
1415
<div class="col-sm">
1516
<div class="mb-3">
@@ -32,4 +33,7 @@
3233
</div>
3334

3435
</div>
36+
{% if jurymember is defined %}
37+
<input type="hidden" name="{{ field_name(form.jurymember) }}" value="{{ jurymember }}">
38+
{% endif %}
3539
{{ form_end(form) }}

0 commit comments

Comments
 (0)