1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use Symfony\Component\Form\Form; |
4
|
|
|
use Symfony\Component\Form\FormError; |
5
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
6
|
|
|
use Symfony\Component\HttpFoundation\Request; |
7
|
|
|
use Symfony\Component\HttpFoundation\StreamedResponse; |
8
|
|
|
|
9
|
|
|
class MatchController extends CRUDController |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* Whether the last edited match has had its ELO changed, requiring an ELO |
13
|
|
|
* recalculation |
14
|
|
|
* |
15
|
|
|
* This is useful so that a confirmation form is shown, asking the user if |
16
|
|
|
* they want to recalculate ELOs |
17
|
|
|
* |
18
|
|
|
* @var bool |
19
|
|
|
*/ |
20
|
|
|
public $recalculateNeeded = false; |
21
|
|
|
|
22
|
|
|
public function listAction(Request $request, Player $me, Team $team = null, Player $player = null, $type = null) |
23
|
|
|
{ |
24
|
|
|
/** @var MatchQueryBuilder $qb */ |
25
|
|
|
$qb = self::getQueryBuilder(); |
26
|
|
|
$currentPage = $this->getCurrentPage(); |
27
|
|
|
|
28
|
|
|
if ($player) { |
29
|
|
|
$team = $player; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
$query = $qb |
33
|
|
|
->sortBy('time')->reverse() |
34
|
|
|
->with($team, $type) |
|
|
|
|
35
|
|
|
->limit(50)->fromPage($currentPage) |
36
|
|
|
; |
37
|
|
|
|
38
|
|
|
$matchType = $request->query->get('type', 'all'); |
39
|
|
|
|
40
|
|
|
if (in_array($matchType, [Match::FUN, Match::OFFICIAL, Match::SPECIAL])) { |
41
|
|
|
$query->where('type')->is($matchType); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$matches = $query->getModels($fast = true); |
45
|
|
|
|
46
|
|
|
/** @var Match $match */ |
47
|
|
|
foreach ($matches as $match) { |
48
|
|
|
// Don't show wrong labels for matches |
49
|
|
|
$match->getOriginalTimestamp()->setTimezone($me->getTimezone()); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
$matches = __::groupBy($matches, function ($match) { |
53
|
|
|
/** @var Match $match */ |
54
|
|
|
$ts = $match->getOriginalTimestamp(); |
55
|
|
|
|
56
|
1 |
|
if ($ts->year !== TimeDate::now()->year) { |
57
|
|
|
return $ts->format(TimeDate::DATE_FULL); |
58
|
1 |
|
} |
59
|
|
|
|
60
|
|
|
return $ts->format(TimeDate::DATE_MEDIUM); |
61
|
1 |
|
}); |
62
|
|
|
|
63
|
|
|
return [ |
64
|
1 |
|
'matchType' => $type, |
65
|
1 |
|
'matches' => $matches, |
66
|
1 |
|
'team' => $team, |
67
|
1 |
|
'currentPage' => $currentPage, |
68
|
|
|
'totalPages' => $qb->countPages(), |
69
|
|
|
]; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function showAction(Match $match) |
73
|
|
|
{ |
74
|
|
|
return array("match" => $match); |
75
|
1 |
|
} |
76
|
|
|
|
77
|
|
|
public function createAction(Player $me) |
78
|
|
|
{ |
79
|
|
|
return $this->create($me, function (Match $match) use ($me) { |
80
|
|
View Code Duplication |
if ($me->canEdit($match) |
|
|
|
|
81
|
|
|
&& $match->isOfficial() |
82
|
|
|
&& (!$match->getTeamA()->isLastMatch($match) |
83
|
|
|
|| !$match->getTeamB()->isLastMatch($match)) |
84
|
|
|
) { |
85
|
|
|
$url = Service::getGenerator()->generate('match_recalculate', array( |
86
|
|
|
'match' => $match->getId(), |
87
|
|
|
)); |
88
|
|
|
|
89
|
|
|
return new RedirectResponse($url); |
90
|
|
|
} |
91
|
|
|
}); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function deleteAction(Player $me, Match $match) |
95
|
|
|
{ |
96
|
|
|
return $this->delete($match, $me, function () use ($match, $me) { |
97
|
|
View Code Duplication |
if ($match->getTeamA()->isLastMatch($match) && $match->getTeamB()->isLastMatch($match)) { |
|
|
|
|
98
|
|
|
$match->resetTeamElos(); |
99
|
|
|
$match->resetPlayerElos(); |
100
|
|
|
} elseif ($me->canEdit($match)) { |
101
|
|
|
$url = Service::getGenerator()->generate('match_recalculate', array( |
102
|
|
|
'match' => $match->getId(), |
103
|
|
|
)); |
104
|
|
|
|
105
|
|
|
return new RedirectResponse($url); |
106
|
|
|
} |
107
|
|
|
}); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
View Code Duplication |
public function editAction(Player $me, Match $match) |
|
|
|
|
111
|
|
|
{ |
112
|
|
|
// TODO: Generating this response is unnecessary |
113
|
|
|
$response = $this->edit($match, $me, "match"); |
114
|
|
|
|
115
|
|
|
if ($this->recalculateNeeded && $match->isOfficial()) { |
116
|
|
|
// Redirect to a confirmation form if we are assigning a new leader |
117
|
|
|
$url = Service::getGenerator()->generate('match_recalculate', array( |
118
|
|
|
'match' => $match->getId(), |
119
|
|
|
)); |
120
|
|
|
|
121
|
|
|
return new RedirectResponse($url); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return $response; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function recalculateAction(Player $me, $match) |
128
|
|
|
{ |
129
|
|
|
$match = Match::get($match); // get a match even if it's deleted |
130
|
|
|
|
131
|
|
|
if (!$me->canEdit($match)) { |
132
|
|
|
throw new ForbiddenException("You are not allowed to edit that match."); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
if (!$match->isOfficial()) { |
136
|
|
|
throw new BadRequestException("You can't recalculate ELO history for a special match."); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
return $this->showConfirmationForm(function () use ($match) { |
140
|
|
|
$response = new StreamedResponse(); |
141
|
|
|
$response->headers->set('Content-Type', 'text/plain'); |
142
|
|
|
$response->setCallback(function () use ($match) { |
143
|
|
|
$this->log(Match::recalculateMatchesSince($match, function ($event) { |
144
|
|
|
switch ($event['type']) { |
145
|
|
|
case 'recalculation.count': |
146
|
|
|
echo $event['type'] . "\n"; |
147
|
|
|
break; |
148
|
|
|
|
149
|
|
|
case 'recalculation.progress': |
150
|
|
|
echo 'm'; |
151
|
|
|
break; |
152
|
|
|
|
153
|
|
|
case 'recalculation.complete': |
154
|
|
|
echo "\n\nCalculation successful\n"; |
155
|
|
|
break; |
156
|
|
|
|
157
|
|
|
default: |
158
|
|
|
break; |
159
|
|
|
} |
160
|
|
|
})); |
161
|
|
|
}); |
162
|
1 |
|
$response->send(); |
163
|
|
|
}, "Do you want to recalculate ELO history for all teams and matches after the specified match?", |
164
|
1 |
|
"ELO history recalculated", |
165
|
|
|
"Recalculate ELOs", |
166
|
|
|
function () use ($match) { |
167
|
1 |
|
if ($match->isDeleted()) { |
168
|
1 |
|
return new RedirectResponse($match->getURL('list')); |
169
|
1 |
|
} |
170
|
1 |
|
|
171
|
|
|
return new RedirectResponse($match->getURL('show')); |
172
|
|
|
}, |
173
|
|
|
"Match/recalculate.html.twig", |
174
|
|
|
$noButton = true |
175
|
1 |
|
); |
176
|
|
|
} |
177
|
|
|
|
178
|
1 |
|
/** |
179
|
|
|
* Echo a string and flush the buffers |
180
|
|
|
* |
181
|
|
|
* Useful for streamed AJAX responses |
182
|
1 |
|
* |
183
|
1 |
|
* @param string $string The string to echo |
184
|
|
|
*/ |
185
|
1 |
|
private function log($string) |
186
|
|
|
{ |
187
|
|
|
echo $string; |
188
|
|
|
ob_flush(); |
189
|
1 |
|
flush(); |
190
|
1 |
|
} |
191
|
1 |
|
|
192
|
|
|
/** |
193
|
|
|
* {@inheritdoc} |
194
|
1 |
|
*/ |
195
|
|
|
protected function getMessages($type, $name = '') |
196
|
1 |
|
{ |
197
|
1 |
|
$messages = parent::getMessages($type, $name); |
198
|
1 |
|
|
199
|
1 |
|
// Don't show the match info on the successful create/edit message |
200
|
|
|
foreach ($messages as &$action) { |
201
|
1 |
|
foreach ($action as &$status) { |
202
|
1 |
|
if (isset($status['named'])) { |
203
|
|
|
$status['named'] = $status['unnamed']; |
204
|
1 |
|
} |
205
|
|
|
} |
206
|
1 |
|
} |
207
|
1 |
|
|
208
|
1 |
|
return $messages; |
209
|
|
|
} |
210
|
1 |
|
|
211
|
1 |
|
/** |
212
|
1 |
|
* @param Form $form |
213
|
|
|
*/ |
214
|
|
|
protected function validate($form) |
215
|
|
|
{ |
216
|
|
|
// Make sure that two different teams participated in a match, i.e. a team |
217
|
1 |
|
// didn't match against itself |
218
|
|
|
$firstTeam = $form->get('first_team')->get('team')->getData(); |
219
|
|
|
$secondTeam = $form->get('second_team')->get('team')->getData(); |
220
|
|
|
|
221
|
|
|
if (!$firstTeam || !$secondTeam) { |
222
|
|
|
return; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if ($firstTeam->isSameAs($secondTeam)) { |
226
|
|
|
$message = "You can't report a match where a team played against itself!"; |
227
|
|
|
$form->addError(new FormError($message)); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$matchType = $form->get('type')->getData(); |
231
|
|
|
|
232
|
|
|
foreach (array('first_team', 'second_team') as $team) { |
233
|
|
|
$input = $form->get($team); |
234
|
|
|
$teamInput = $input->get('team'); |
235
|
|
|
$teamParticipants = $input->get('participants'); |
236
|
|
|
|
237
|
|
|
if ($matchType === Match::FUN) { |
238
|
|
|
if (!$teamInput->getData() instanceof ColorTeam) { |
239
|
|
|
$message = "Please enter a team color for fun and special matches."; |
240
|
|
|
$teamInput->addError(new FormError($message)); |
241
|
|
|
} |
242
|
|
|
} elseif ($matchType === Match::OFFICIAL) { |
243
|
|
|
if ($teamInput->getData() instanceof ColorTeam) { |
244
|
|
|
$participants = $teamParticipants->getData(); |
245
|
|
|
|
246
|
|
|
if (empty($participants)) { |
247
|
|
|
$message = 'A player roster is necessary for a color team for a mixed official match.'; |
248
|
|
|
$teamInput->addError(new FormError($message)); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @param Form $form |
257
|
|
|
* @param Match $match |
258
|
|
|
*/ |
259
|
|
|
protected function validateEdit($form, $match) |
260
|
|
|
{ |
261
|
|
|
if ($match->isOfficial() && $form->get('type')->getData() !== Match::OFFICIAL) { |
262
|
|
|
$message = "You cannot change this match's type."; |
263
|
|
|
$form->get('type')->addError(new FormError($message)); |
264
|
|
|
} elseif (!$match->isOfficial() && $form->get('type')->getData() === Match::OFFICIAL) { |
265
|
|
|
$message = "You can't make this an official match."; |
266
|
|
|
$form->get('type')->addError(new FormError($message)); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.