<?php
declare(strict_types=1);
namespace App\Controller\Admin\FriendsOfSylius\ImportExport;
use App\Entity\Import\SavedImport;
use App\Form\Type\ImportExport\ConfirmedImportType;
use App\Form\Type\ImportExport\ImportType;
use Doctrine\ORM\EntityManagerInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Exception\ImporterException;
use FriendsOfSylius\SyliusImportExportPlugin\Importer\ImporterInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Importer\ImporterRegistry;
use FriendsOfSylius\SyliusImportExportPlugin\Importer\ImporterResult;
use Knp\Component\Pager\PaginatorInterface;
use Port\Reader\ReaderFactory;
use Sylius\Component\Registry\ServiceRegistryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
final class ImportDataController extends AbstractController
{
public function __construct(
private ServiceRegistryInterface $registry,
private FlashBagInterface $flashBag,
private FormFactoryInterface $formFactory,
private \Twig_Environment $twig,
private ReaderFactory $readerFactory,
private PaginatorInterface $paginator,
private EntityManagerInterface $entityManager,
) {
}
// partial view for upload import form
public function importFormAction(Request $request): Response
{
$importer = $request->attributes->get('resource');
$referer = $request->attributes->get('referer');
$form = $this->getForm($importer);
if ($importer === 'product') {
// overrided product processor
$importer = 'app_product_base';
} elseif ($importer === 'taxonomy') {
$importer = 'app_product_taxon';
}
// get importer service to get headers keys
$name = ImporterRegistry::buildServiceName($importer, 'csv');
/** @var ImporterInterface $service */
$service = $this->registry->get($name);
$headersKeys = $service->getHeaders();
$content = $this->twig->render(
'@FOSSyliusImportExportPlugin/Crud/import_form.html.twig',
['form' => $form->createView(), 'resource' => $importer, 'headersKeys' => $headersKeys, 'referer' => $referer]
);
return new Response($content);
}
// on submit upload import form
public function importAction(Request $request): Response
{
$importer = $request->attributes->get('resource');
$form = $this->getForm($importer);
$form->handleRequest($request);
if ($importer === 'product') {
// overrided product processor
$importer = 'app_product_base';
}
$referer = $request->headers->get('referer');
// import form
if ($form->isSubmitted() && $form->isValid()) {
$preview = $this->getDataPreviewAction($form);
$previewJson = json_encode($preview);
if (strlen($previewJson) >= 2000000000) {
$this->flashBag->add('error', "l'import est trop important, veuillez le fractionner puis recommencer");
return new RedirectResponse($referer);
}
$import = new SavedImport();
$import->setType($importer);
$import->setJsonData($previewJson);
$this->entityManager->persist($import);
$this->entityManager->flush();
return new RedirectResponse($this->generateUrl('app_admin_import_preview', [
'keys' => $form->getData()['headerKeys'],
'referer' => $referer,
'importId' => $import->getId(),
'format' => $form->getData()['format'],
'importer' => $importer, ]));
}
return new RedirectResponse($referer);
}
// view preview with confirmed form
public function previewAction(Request $request)
{
$importId = $request->query->get('importId');
$keysJson = $request->query->get('keys');
$referer = $request->query->get('referer');
$format = $request->query->get('format');
$importer = $request->query->get('importer');
if (!$importId || !$keysJson || !$referer || !$format || !$importer) {
throw new ImporterException('Data not found');
}
$import = $this->container->get('doctrine')->getRepository(SavedImport::class)->find($importId);
if (!$import) {
throw new ImporterException('Data not found');
}
$preview = json_decode($import->getJsonData(), true);
$result = $this->paginator->paginate(
$preview[1], // datas
$request->query->getInt('page', 1), // current page, 1 if we can't know
30 // Number of result per page
);
// define default data that can be filled during preview
$keys = json_decode($keysJson, true);
$defaultData = [];
foreach ($keys['optional'] as $optionalKey) {
if (!in_array($optionalKey, $keys['mandatory']) && !in_array($optionalKey, $preview[0])) {
$defaultData[] = $optionalKey;
}
}
// confirmed form with action to confirmImportDataAction
$confirmedForm = $this->formFactory->create(ConfirmedImportType::class, null, [
'dataToImport' => $preview[1],
'format' => $format,
'referer' => $referer,
'importer' => $importer,
'defaultData' => $defaultData, ]);
$confirmedForm->handleRequest($request);
$content = $this->twig->render(
'@SyliusAdmin/Import/preview.html.twig',
[
'dataHeader' => $preview[0], 'result' => $result,
'resultPerPage' => $result->getItemNumberPerPage(),
'count' => $result->getTotalItemCount(),
'referer' => $referer,
'form' => $confirmedForm->createView(), ]
);
return new Response($content);
}
// after submit confirm form
public function confirmImportDataAction(Request $request)
{
$referer = $request->request->get('app_confirm_import')['referer'];
$serializedData = $request->request->get('app_confirm_import')['data'];
$importer = $request->request->get('app_confirm_import')['importer'];
$format = $request->request->get('app_confirm_import')['format'];
if (!$referer || !$serializedData || !$importer || !$format) {
throw new ImporterException('Data not found');
}
$data = json_decode($serializedData, true);
// find filled default datas
$defaultDatas = [];
foreach ($request->request->get('app_confirm_import') as $key => $value) {
if (substr($key, 0, 13) === 'default_data_' && $value && $value !== '') {
$defaultDatas[str_replace('default_data_', '', $key)] = $value;
}
}
try {
$this->importDataFromArray($importer, $referer, $data, $format, $defaultDatas);
} catch (\Throwable $exception) {
$this->flashBag->add('error', $exception->getMessage());
}
return new RedirectResponse($referer);
}
// get data form preview
private function getDataPreviewAction($form): array
{
/** @var UploadedFile|null $file */
$file = $form->get('import-data')->getData();
if (null === $file) {
throw new ImporterException('No file selected');
}
$path = $file->getRealPath();
if (false === $path) {
throw new ImporterException(sprintf('File %s could not be loaded', $file->getClientOriginalName()));
}
$reader = $this->readerFactory->getReader(new \SplFileObject($path));
$dataPreview = $header = [];
foreach ($reader as $i => $row) {
if (!$row) {
continue;
}
if ($i === 1) {
$header = array_keys($row);
}
$dataPreview[$i] = $row;
}
return [$header, $dataPreview];
}
// get upload form
private function getForm(string $importerType): FormInterface
{
return $this->formFactory->create(ImportType::class, null, ['importer_type' => $importerType]);
}
// import data from array from preview
private function importDataFromArray(string $importer, string $referer, array $data, string $format, array $defaultData): void
{
$name = ImporterRegistry::buildServiceName($importer, $format);
if (!$this->registry->has($name)) {
$message = sprintf("No importer found of type '%s' for format '%s'", $importer, $format);
throw new ImporterException($message);
}
/** @var ImporterInterface $service */
$service = $this->registry->get($name);
/** @var ImporterResult $result */
$result = $service->importFromArray($data, $defaultData);
$message = sprintf(
'Import de %s (Temps en ms: %s, %s lignes importées, %s lignes passées, %s lignes en erreur)',
$name,
$result->getDuration(),
count($result->getSuccessRows()),
count($result->getSkippedRows()),
count($result->getFailedRows())
);
$this->flashBag->add('success', $message);
if ($result->getMessage() !== null) {
throw new ImporterException($result->getMessage());
}
}
// import data from form (without preview)
private function importData(string $importer, FormInterface $form): void
{
$format = $form->get('format')->getData();
$name = ImporterRegistry::buildServiceName($importer, $format);
if (!$this->registry->has($name)) {
$message = sprintf("No importer found of type '%s' for format '%s'", $importer, $format);
throw new ImporterException($message);
}
/** @var UploadedFile|null $file */
$file = $form->get('import-data')->getData();
/** @var ImporterInterface $service */
$service = $this->registry->get($name);
if (null === $file) {
throw new ImporterException('No file selected');
}
$path = $file->getRealPath();
if (false === $path) {
throw new ImporterException(sprintf('File %s could not be loaded', $file->getClientOriginalName()));
}
/** @var ImporterResult $result */
$result = $service->import($path);
$message = sprintf(
'Import de %s (Temps en ms: %s, %s lignes importées, %s lignes passées, %s lignes en erreur)',
$name,
$result->getDuration(),
count($result->getSuccessRows()),
count($result->getSkippedRows()),
count($result->getFailedRows())
);
$this->flashBag->add('success', $message);
if ($result->getMessage() !== null) {
throw new ImporterException($result->getMessage());
}
}
}