Lines 96.87% 31 / 32
Methods 80.00% 4 / 5
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __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
28final 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}