11
11
use App \Entity \TeamCategory ;
12
12
use App \Form \Type \ContestExportType ;
13
13
use App \Form \Type \ContestImportType ;
14
+ use App \Form \Type \ExportResultsType ;
14
15
use App \Form \Type \ICPCCmsType ;
15
16
use App \Form \Type \JsonImportType ;
16
17
use App \Form \Type \ProblemsImportType ;
46
47
use Symfony \Contracts \HttpClient \Exception \RedirectionExceptionInterface ;
47
48
use Symfony \Contracts \HttpClient \Exception \ServerExceptionInterface ;
48
49
use Symfony \Contracts \HttpClient \Exception \TransportExceptionInterface ;
50
+ use Twig \Environment ;
49
51
50
52
#[Route(path: '/jury/import-export ' )]
51
53
#[IsGranted('ROLE_JURY ' )]
@@ -63,6 +65,7 @@ public function __construct(
63
65
KernelInterface $ kernel ,
64
66
#[Autowire('%domjudge.version% ' )]
65
67
protected readonly string $ domjudgeVersion ,
68
+ protected readonly Environment $ twig ,
66
69
) {
67
70
parent ::__construct ($ em , $ eventLogService , $ dj , $ kernel );
68
71
}
@@ -257,21 +260,73 @@ public function indexAction(Request $request): Response
257
260
return $ this ->redirectToRoute ('jury_import_export ' );
258
261
}
259
262
260
- /** @var TeamCategory[] $teamCategories */
261
- $ teamCategories = $ this ->em ->createQueryBuilder ()
262
- ->from (TeamCategory::class, 'c ' , 'c.categoryid ' )
263
- ->select ('c.sortorder, c.name ' )
264
- ->where ('c.visible = 1 ' )
265
- ->orderBy ('c.sortorder ' )
266
- ->getQuery ()
267
- ->getResult ();
268
- $ sortOrders = [];
269
- foreach ($ teamCategories as $ teamCategory ) {
270
- $ sortOrder = $ teamCategory ['sortorder ' ];
271
- if (!array_key_exists ($ sortOrder , $ sortOrders )) {
272
- $ sortOrders [$ sortOrder ] = [];
263
+ $ exportResultsForm = $ this ->createForm (ExportResultsType::class);
264
+
265
+ $ exportResultsForm ->handleRequest ($ request );
266
+
267
+ if ($ exportResultsForm ->isSubmitted () && $ exportResultsForm ->isValid ()) {
268
+ $ contest = $ this ->dj ->getCurrentContest ();
269
+ if ($ contest === null ) {
270
+ throw new BadRequestHttpException ('No current contest ' );
273
271
}
274
- $ sortOrders [$ sortOrder ][] = $ teamCategory ['name ' ];
272
+
273
+ $ data = $ exportResultsForm ->getData ();
274
+ $ format = $ data ['format ' ];
275
+ $ sortOrder = $ data ['sortorder ' ];
276
+ $ individuallyRanked = $ data ['individually_ranked ' ];
277
+ $ honors = $ data ['honors ' ];
278
+
279
+ $ extension = match ($ format ) {
280
+ 'html_inline ' , 'html_download ' => 'html ' ,
281
+ 'tsv ' => 'tsv ' ,
282
+ default => throw new BadRequestHttpException ('Invalid format ' ),
283
+ };
284
+ $ contentType = match ($ format ) {
285
+ 'html_inline ' => 'text/html ' ,
286
+ 'html_download ' => 'text/html ' ,
287
+ 'tsv ' => 'text/csv ' ,
288
+ default => throw new BadRequestHttpException ('Invalid format ' ),
289
+ };
290
+ $ contentDisposition = match ($ format ) {
291
+ 'html_inline ' => 'inline ' ,
292
+ 'html_download ' , 'tsv ' => 'attachment ' ,
293
+ default => throw new BadRequestHttpException ('Invalid format ' ),
294
+ };
295
+ $ filename = 'results. ' . $ extension ;
296
+
297
+ $ response = new StreamedResponse ();
298
+ $ response ->setCallback (function () use (
299
+ $ format ,
300
+ $ sortOrder ,
301
+ $ individuallyRanked ,
302
+ $ honors
303
+ ) {
304
+ if ($ format === 'tsv ' ) {
305
+ $ data = $ this ->importExportService ->getResultsData (
306
+ $ sortOrder ->sort_order ,
307
+ $ individuallyRanked ,
308
+ $ honors ,
309
+ );
310
+
311
+ echo "results \t1 \n" ;
312
+ foreach ($ data as $ row ) {
313
+ echo implode ("\t" , array_map (fn ($ field ) => Utils::toTsvField ((string )$ field ), $ row ->toArray ())) . "\n" ;
314
+ }
315
+ } else {
316
+ echo $ this ->getResultsHtml (
317
+ $ sortOrder ->sort_order ,
318
+ $ individuallyRanked ,
319
+ $ honors ,
320
+ );
321
+ }
322
+ });
323
+ $ response ->headers ->set ('Content-Type ' , $ contentType );
324
+ $ response ->headers ->set ('Content-Disposition ' , "$ contentDisposition; filename= \"$ filename \"" );
325
+ $ response ->headers ->set ('Content-Transfer-Encoding ' , 'binary ' );
326
+ $ response ->headers ->set ('Connection ' , 'Keep-Alive ' );
327
+ $ response ->headers ->set ('Accept-Ranges ' , 'bytes ' );
328
+
329
+ return $ response ;
275
330
}
276
331
277
332
return $ this ->render ('jury/import_export.html.twig ' , [
@@ -282,16 +337,13 @@ public function indexAction(Request $request): Response
282
337
'contest_export_form ' => $ contestExportForm ,
283
338
'contest_import_form ' => $ contestImportForm ,
284
339
'problems_import_form ' => $ problemsImportForm ,
285
- 'sort_orders ' => $ sortOrders ,
340
+ 'export_results_form ' => $ exportResultsForm ,
286
341
]);
287
342
}
288
343
289
344
#[Route(path: '/export/{type<groups|teams|wf_results|full_results>}.tsv ' , name: 'jury_tsv_export ' )]
290
- public function exportTsvAction (
291
- string $ type ,
292
- #[MapQueryParameter(name: 'sort_order ' )]
293
- ?int $ sortOrder ,
294
- ): Response {
345
+ public function exportTsvAction (string $ type ): Response
346
+ {
295
347
$ data = [];
296
348
$ tsvType = $ type ;
297
349
try {
@@ -302,14 +354,6 @@ public function exportTsvAction(
302
354
case 'teams ' :
303
355
$ data = $ this ->importExportService ->getTeamData ();
304
356
break ;
305
- case 'wf_results ' :
306
- $ data = $ this ->importExportService ->getResultsData ($ sortOrder );
307
- $ tsvType = 'results ' ;
308
- break ;
309
- case 'full_results ' :
310
- $ data = $ this ->importExportService ->getResultsData ($ sortOrder , full: true );
311
- $ tsvType = 'results ' ;
312
- break ;
313
357
}
314
358
} catch (BadRequestHttpException $ e ) {
315
359
$ this ->addFlash ('danger ' , $ e ->getMessage ());
@@ -322,9 +366,6 @@ public function exportTsvAction(
322
366
echo sprintf ("%s \t%s \n" , $ tsvType , $ version );
323
367
foreach ($ data as $ row ) {
324
368
// Utils::toTsvFields handles escaping of reserved characters.
325
- if ($ row instanceof ResultRow) {
326
- $ row = $ row ->toArray ();
327
- }
328
369
echo implode ("\t" , array_map (fn ($ field ) => Utils::toTsvField ((string )$ field ), $ row )) . "\n" ;
329
370
}
330
371
});
@@ -335,29 +376,22 @@ public function exportTsvAction(
335
376
return $ response ;
336
377
}
337
378
338
- #[Route(path: '/export/{type<wf_results|full_results| clarifications>} .html ' , name: 'jury_html_export ' )]
339
- public function exportHtmlAction ( Request $ request , string $ type ): Response
379
+ #[Route(path: '/export/clarifications.html ' , name: 'jury_html_export_clarifications ' )]
380
+ public function exportClarificationsHtmlAction ( ): Response
340
381
{
341
382
try {
342
- switch ($ type ) {
343
- case 'wf_results ' :
344
- return $ this ->getResultsHtml ($ request );
345
- case 'full_results ' :
346
- return $ this ->getResultsHtml ($ request , full: true );
347
- case 'clarifications ' :
348
- return $ this ->getClarificationsHtml ();
349
- default :
350
- $ this ->addFlash ('danger ' , "Unknown export type ' " . $ type . "' requested. " );
351
- return $ this ->redirectToRoute ('jury_import_export ' );
352
- }
383
+ return $ this ->getClarificationsHtml ();
353
384
} catch (BadRequestHttpException $ e ) {
354
385
$ this ->addFlash ('danger ' , $ e ->getMessage ());
355
386
return $ this ->redirectToRoute ('jury_import_export ' );
356
387
}
357
388
}
358
389
359
- protected function getResultsHtml (Request $ request , bool $ full = false ): Response
360
- {
390
+ protected function getResultsHtml (
391
+ int $ sortOrder ,
392
+ bool $ individuallyRanked ,
393
+ bool $ honors
394
+ ): string {
361
395
/** @var TeamCategory[] $categories */
362
396
$ categories = $ this ->em ->createQueryBuilder ()
363
397
->from (TeamCategory::class, 'c ' , 'c.categoryid ' )
@@ -392,9 +426,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons
392
426
$ regionWinners = [];
393
427
$ rankPerTeam = [];
394
428
395
- $ sortOrder = $ request ->query ->getInt ('sort_order ' );
396
-
397
- foreach ($ this ->importExportService ->getResultsData ($ sortOrder , full: $ full ) as $ row ) {
429
+ foreach ($ this ->importExportService ->getResultsData ($ sortOrder , $ individuallyRanked , $ honors ) as $ row ) {
398
430
$ team = $ teamNames [$ row ->teamId ];
399
431
$ rankPerTeam [$ row ->teamId ] = $ row ->rank ;
400
432
@@ -421,7 +453,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons
421
453
}
422
454
if ($ row ['rank ' ] === null ) {
423
455
$ honorable [] = $ row ['team ' ];
424
- } elseif (in_array ($ row ['award ' ], ['Highest Honors ' , 'High Honors ' , 'Honors ' ], true )) {
456
+ } elseif (in_array ($ row ['award ' ], ['Ranked ' , ' Highest Honors ' , 'High Honors ' , 'Honors ' ], true )) {
425
457
$ ranked [$ row ['award ' ]][] = $ row ;
426
458
} else {
427
459
$ awarded [] = $ row ;
@@ -432,7 +464,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons
432
464
433
465
$ collator = new Collator ('en_US ' );
434
466
$ collator ->sort ($ honorable );
435
- foreach ($ ranked as $ award => &$ rankedTeams ) {
467
+ foreach ($ ranked as &$ rankedTeams ) {
436
468
usort ($ rankedTeams , function (array $ a , array $ b ) use ($ collator ): int {
437
469
if ($ a ['rank ' ] !== $ b ['rank ' ]) {
438
470
return $ a ['rank ' ] <=> $ b ['rank ' ];
@@ -494,16 +526,10 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons
494
526
'firstToSolve ' => $ firstToSolve ,
495
527
'domjudgeVersion ' => $ this ->domjudgeVersion ,
496
528
'title ' => sprintf ('Results for %s ' , $ contest ->getName ()),
497
- 'download ' => $ request ->query ->getBoolean ('download ' ),
498
529
'sortOrder ' => $ sortOrder ,
499
530
];
500
- $ response = $ this ->render ('jury/export/results.html.twig ' , $ data );
501
531
502
- if ($ request ->query ->getBoolean ('download ' )) {
503
- $ response ->headers ->set ('Content-disposition ' , 'attachment; filename=results.html ' );
504
- }
505
-
506
- return $ response ;
532
+ return $ this ->twig ->render ('jury/export/results.html.twig ' , $ data );
507
533
}
508
534
509
535
protected function getClarificationsHtml (): Response
0 commit comments