Lines
96.87%
31 / 32
Functions and Methods
80.00%
4 / 5
Classes and Traits
0.00%
0 / 1
| Name | Lines | Functions and Methods | CRAP | Classes and Traits | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Import | 96.87% | 31 / 32 | 80.00% | 4 / 5 | 13 | 0.00% | 0 / 1 | |||
| __invoke | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 5 | |||||
| importStats | 80.00% | 4 / 5 | 0.00% | 0 / 1 | 2.03 | |||||
| statsImported | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 2 | |||||
| medalsImported | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 2 | |||||
| flashErrors | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 2 | |||||
| 1 | <?php | |
| 2 | ||
| 3 | declare(strict_types=1); | |
| 4 | ||
| 5 | namespace App\Controller\AgentStat; | |
| 6 | ||
| 7 | use App\Controller\BaseController; | |
| 8 | use App\Enum\UserRole; | |
| 9 | use App\Form\StatsImportType; | |
| 10 | use App\Import\AgentStatCsvImporter; | |
| 11 | use App\Import\CsvImportResult; | |
| 12 | use App\Import\CustomMedalsImportResult; | |
| 13 | use App\Import\CustomMedalsJsonImporter; | |
| 14 | use App\Notifier\ChatActivity; | |
| 15 | use Symfony\Component\HttpFoundation\File\UploadedFile; | |
| 16 | use Symfony\Component\HttpFoundation\Request; | |
| 17 | use Symfony\Component\HttpFoundation\Response; | |
| 18 | use Symfony\Component\Routing\Attribute\Route; | |
| 19 | use Symfony\Component\Security\Http\Attribute\IsGranted; | |
| 20 | use Symfony\Contracts\Translation\TranslatorInterface; | |
| 21 | ||
| 22 | /** | |
| 23 | * Imports the user's stats from a CSV file or custom medals from a JSON file, e.g. 4e-website's | |
| 24 | * exports; the result is shown on the user's stats. | |
| 25 | */ | |
| 26 | #[Route('/agent-stats/import', name: 'app_agent_stat_import', methods: ['GET', 'POST'])] | |
| 27 | #[IsGranted(UserRole::USER->value)] | |
| 28 | final class Import extends BaseController | |
| 29 | { | |
| 30 | /** At most this many rows that were not imported are listed. */ | |
| 31 | private const int SHOWN_ERRORS = 10; | |
| 32 | ||
| 33 | public function __invoke( | |
| 34 | Request $request, | |
| 35 | AgentStatCsvImporter $importer, | |
| 36 | CustomMedalsJsonImporter $medalsImporter, | |
| 37 | TranslatorInterface $translator, | |
| 38 | ChatActivity $chatActivity, | |
| 39 | ): Response { | |
| 40 | $form = $this->createForm(StatsImportType::class); | |
| 41 | $form->handleRequest($request); | |
| 42 | ||
| 43 | $file = $form->get('file')->getData(); | |
| 44 | ||
| 45 | // The form, again with what is wrong (render() answers 422 for an invalid one) | |
| 46 | if (!$form->isSubmitted() || !$form->isValid() || !$file instanceof UploadedFile) { | |
| 47 | return $this->render('agent_stat/import.html.twig', ['form' => $form]); | |
| 48 | } | |
| 49 | ||
| 50 | if ('json' === \strtolower($file->getClientOriginalExtension())) { | |
| 51 | $this->medalsImported($medalsImporter->import($this->getUser(), (string) \file_get_contents($file->getPathname())), $translator); | |
| 52 | } else { | |
| 53 | $result = $this->importStats($importer, $file); | |
| 54 | $chatActivity->statsImported($this->getUser(), $result->imported); | |
| 55 | $this->statsImported($result, $translator); | |
| 56 | } | |
| 57 | ||
| 58 | return $this->redirectToRoute('app_agent_stat_index', [], Response::HTTP_SEE_OTHER); | |
| 59 | } | |
| 60 | ||
| 61 | private function importStats(AgentStatCsvImporter $importer, UploadedFile $file): CsvImportResult | |
| 62 | { | |
| 63 | $csv = \fopen($file->getPathname(), 'r'); | |
| 64 | if (false === $csv) { | |
| 65 | throw new \RuntimeException('The uploaded file cannot be read.'); | |
| 66 | } | |
| 67 | ||
| 68 | try { | |
| 69 | return $importer->import($this->getUser(), $csv); | |
| 70 | } finally { | |
| 71 | \fclose($csv); | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | private function statsImported(CsvImportResult $result, TranslatorInterface $translator): void | |
| 76 | { | |
| 77 | $this->addFlash($result->imported > 0 ? 'success' : 'info', $translator->trans('stats_import.done', [ | |
| 78 | '%imported%' => $result->imported, | |
| 79 | '%skipped%' => $result->skipped, | |
| 80 | ])); | |
| 81 | $this->flashErrors($result->errors, $translator); | |
| 82 | } | |
| 83 | ||
| 84 | private function medalsImported(CustomMedalsImportResult $result, TranslatorInterface $translator): void | |
| 85 | { | |
| 86 | $this->addFlash($result->added + $result->upgraded > 0 ? 'success' : 'info', $translator->trans('stats_import.medals_done', [ | |
| 87 | '%added%' => $result->added, | |
| 88 | '%upgraded%' => $result->upgraded, | |
| 89 | '%kept%' => $result->kept, | |
| 90 | ])); | |
| 91 | $this->flashErrors($result->errors, $translator); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * @param list<string> $errors what was not imported | |
| 96 | */ | |
| 97 | private function flashErrors(array $errors, TranslatorInterface $translator): void | |
| 98 | { | |
| 99 | if ([] === $errors) { | |
| 100 | return; | |
| 101 | } | |
| 102 | ||
| 103 | // The texts come from the file: escaped, flashes are shown as HTML | |
| 104 | $lines = \array_map(\htmlspecialchars(...), \array_slice($errors, 0, self::SHOWN_ERRORS)); | |
| 105 | $this->addFlash('warning', $translator->trans('stats_import.errors', ['%count%' => \count($errors)]) | |
| 106 | .'<ul class="mb-0"><li>'.\implode('</li><li>', $lines).'</li></ul>'); | |
| 107 | } | |
| 108 | } |