diff --git a/Command/ImportProducts.php b/Command/ImportProducts.php index 27267c0..78a9fea 100644 --- a/Command/ImportProducts.php +++ b/Command/ImportProducts.php @@ -29,45 +29,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $this->initRequest(); - $baseDir = $input->getArgument(self::DIR_PATH); - $finder = Finder::create() - ->files() - ->in($baseDir) - ->depth(0) - ->name('*.csv') - ; - - $output->writeln("Fetching directory $baseDir"); - - $return = Command::SUCCESS; - - $count = $errors = 0; - - foreach ($finder->getIterator() as $file) { - $output->writeln("Starting to import : ".$file->getBasename().""); - - $count++; - - try { - $this->csvProductImporterService->importProductsFromCsv($file->getPathname(), $baseDir); - - $output->writeln('Import is a success !'); - } catch (\Exception $e) { - Tlog::getInstance()->addError("Erreur lors de l'importation : ".$e->getMessage()); - $output->writeln('Error : '.$e->getMessage().''); - if ($e->getPrevious()) { - $output->writeln('Caused by : '.$e->getPrevious()->getMessage().''); - } - - $return = Command::FAILURE; - - $errors++; - } - } - - $output->writeln("$count file(s) processed, $errors error(s)."); - - return $return; + return $this->csvProductImporterService->importFromDirectory($input->getArgument(self::DIR_PATH)) ? + Command::SUCCESS : + Command::FAILURE; } } diff --git a/Config/config.xml b/Config/config.xml index 42d3733..dc70d14 100755 --- a/Config/config.xml +++ b/Config/config.xml @@ -3,4 +3,11 @@ + + + + + + + diff --git a/Config/module.xml b/Config/module.xml index 5989675..30a7061 100755 --- a/Config/module.xml +++ b/Config/module.xml @@ -7,7 +7,7 @@ CSV Importer - 1.0.4 + 1.0.5 Thelia info@thelia.net diff --git a/Controller/ConfigurationController.php b/Controller/ConfigurationController.php new file mode 100644 index 0000000..d4ace41 --- /dev/null +++ b/Controller/ConfigurationController.php @@ -0,0 +1,83 @@ + + * Date: 14/11/2024 09:23 + */ +namespace CsvImporter\Controller; + +use CsvImporter\Service\CsvProductImporterService; +use Propel\Runtime\Propel; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Log\Destination\TlogDestinationFile; +use Thelia\Log\Destination\TlogDestinationRotatingFile; +use Thelia\Log\Tlog; +use Thelia\Tools\URL; + +/** + * @Route("/admin/module/csvimporter") + */ +class ConfigurationController extends BaseAdminController +{ + protected string $logFile = THELIA_LOG_DIR . 'catalog-import-log.txt'; + + /** + * Import manuel du catalogue + * + * @Route("/import", methods="GET") + */ + public function import(CsvProductImporterService $csvProductImporterService): Response + { + @unlink($this->logFile); + + Tlog::getInstance() + ->setLevel(Tlog::INFO) + ->setDestinations(TlogDestinationFile::class) + ->setConfig(TlogDestinationFile::class, TlogDestinationFile::VAR_PATH_FILE, $this->logFile) + + ->setFiles('*') + ->setPrefix('[#LEVEL] #DATE #HOUR:'); + + // Pas de log des requetes SQL + Propel::getConnection('TheliaMain')->useDebug(false); + + $catalogDir = THELIA_LOCAL_DIR . 'Catalogue'; + + if (! is_dir($catalogDir)) { + return $this->generateRedirect(URL::getInstance()?->absoluteUrl( + '/admin/module/CsvImporter', + [ 'error' => 'Répertoire ' . $catalogDir . ' non trouvé.' ]) + ); + } + + $csvProductImporterService->importFromDirectory($catalogDir); + + return $this->generateRedirect(URL::getInstance()?->absoluteUrl('/admin/module/CsvImporter', [ 'done' => 1 ])); + } + + /** + * Import manuel du catalogue + * + * @Route("/log", methods="GET") + */ + public function getLogFile(): Response + { + return new Response( + @file_get_contents($this->logFile), + 200, + array( + 'Content-type' => "text/plain", + 'Content-Disposition' => 'Attachment;filename=csv-import-log.txt' + ) + ); + } +} diff --git a/Hook/HookManager.php b/Hook/HookManager.php new file mode 100644 index 0000000..361959a --- /dev/null +++ b/Hook/HookManager.php @@ -0,0 +1,64 @@ + + * Date: 14/11/2024 09:27 + */ +namespace CsvImporter\Hook; + +use BestSellers\BestSellers; +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Tools\URL; + +/** + * + */ +class HookManager extends BaseHook +{ + protected const MAX_TRACE_SIZE_IN_BYTES = 40000; + + public function onModuleConfiguration(HookRenderEvent $event) + { + $logFilePath = THELIA_LOG_DIR . 'catalog-import-log.txt'; + + $traces = @file_get_contents($logFilePath); + + // Limiter la taille des traces à 1MO + if (strlen($traces) > self::MAX_TRACE_SIZE_IN_BYTES) { + $traces = substr($traces, strlen($traces) - self::MAX_TRACE_SIZE_IN_BYTES); + + // Cut a first line break; + if (false !== $lineBreakPos = strpos($traces, "\n")) { + $traces = substr($traces, $lineBreakPos+1); + } + } + + $event->add( + $this->render( + 'module-configuration.html', + [ 'trace_content' => nl2br($traces) ] + ) + ); + } + + public function onMainTopMenuTools(HookRenderBlockEvent $event) + { + $event->add( + [ + 'id' => 'csvimporter_menu', + 'class' => '', + 'url' => URL::getInstance()->absoluteUrl('/admin/module/CsvImporter'), + 'title' =>"Import catalogue CSV" + ] + ); + } +} diff --git a/Service/CsvProductImporterService.php b/Service/CsvProductImporterService.php index 990b386..ca1f1ac 100644 --- a/Service/CsvProductImporterService.php +++ b/Service/CsvProductImporterService.php @@ -14,7 +14,9 @@ use Propel\Runtime\Exception\PropelException; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\File\UploadedFile; use Thelia\Core\Event\Attribute\AttributeAvCreateEvent; use Thelia\Core\Event\Attribute\AttributeCreateEvent; @@ -97,6 +99,56 @@ public function __construct( { } + public function importFromDirectory(string $path, ?OutputInterface $output = null): bool + { + $finder = Finder::create() + ->files() + ->in($path) + ->ignoreDotFiles(true) + ->depth(0) + ->name('*.csv') + ; + + $output?->writeln("Fetching directory $path"); + + $count = $errors = 0; + + $success = true; + + foreach ($finder->getIterator() as $file) { + Tlog::getInstance()->info("Importation du fichier $file"); + + $output?->writeln("Starting to import : ".$file->getBasename().""); + + $count++; + + try { + $this->importProductsFromCsv($file->getPathname(), $path); + + $output?->writeln('Import is a success !'); + + Tlog::getInstance()->info("Fichier $file importé."); + } catch (\Exception $e) { + Tlog::getInstance()->addError("Erreur lors de l'importation de $file : ".$e->getMessage()); + $output?->writeln('Error : '.$e->getMessage().''); + + if ($e->getPrevious()) { + $output?->writeln('Caused by : '.$e->getPrevious()->getMessage().''); + } + + $success = false; + + $errors++; + } + } + + Tlog::getInstance()->info("$count fichiers(s) traités, $errors erreur(s)."); + + $output?->writeln("$count file(s) processed, $errors error(s)."); + + return $success; + } + /** * @throws PropelException * @throws \Exception @@ -115,31 +167,35 @@ public function importProductsFromCsv(string $filePath, string $basedir, Country throw new \RuntimeException("Cannot open file: $filePath"); } + $line = 0; + $headers = fgetcsv($handle); while (($data = fgetcsv($handle)) !== false) { + $line++; + if (!$productData = array_combine($headers, $data)) { throw new \RuntimeException('Problem while combining headers and data.'); } $productData = $this->csvParser->mapToArray($productData); if (!$productData[self::REF_COLUMN]) { - Tlog::getInstance()->addWarning('Missing Product reference'); + Tlog::getInstance()->addWarning("Line $line: Missing Product reference"); continue; } if (!$productData[self::TITLE_COLUMN]) { - Tlog::getInstance()->addWarning('Missing Product title'); + Tlog::getInstance()->addWarning("Line $line: Missing Product title"); continue; } if (!$productData[self::LEVEL1_COLUMN]) { - Tlog::getInstance()->addWarning('Missing Product category'); + Tlog::getInstance()->addWarning("Line $line: Missing Product category"); continue; } if (!$productData[self::TAX_RULE_COLUMN]) { - Tlog::getInstance()->addWarning('Missing Product tax rule'); + Tlog::getInstance()->addWarning("Line $line: Missing Product tax rule"); continue; } if (!$productData[self::PRICE_EXCL_TAX_COLUMN]) { - Tlog::getInstance()->addWarning('Missing Product price for:' . $productData[self::REF_COLUMN]); + Tlog::getInstance()->addWarning("Line $line: Missing Product price for:" . $productData[self::REF_COLUMN]); continue; } $product = $this->findOrCreateProduct( @@ -153,7 +209,6 @@ public function importProductsFromCsv(string $filePath, string $basedir, Country } fclose($handle); - Tlog::getInstance()->addInfo('End of products import.'); } /** @@ -271,6 +326,8 @@ private function findOrCreateCategory(array $productData, string $locale, Catego ->setVisible(1); $this->dispatcher->dispatch($createEvent, TheliaEvents::CATEGORY_CREATE); $category = $createEvent->getCategory(); + + Tlog::getInstance()->info('Created catégory ' . $productData[$level]); } $this->findOrCreateCategory($productData, $locale, $category, $this->incrementLevel($level)); @@ -304,7 +361,7 @@ private function findOrCreateProduct(array $productData, Country $country, strin } $newProduct = $this->dispatchProductEvent(new ProductCreateEvent(), $productData, $locale, $category, $country, true); - Tlog::getInstance()->addInfo('Produit créé : ' . $productData[self::TITLE_COLUMN]); + Tlog::getInstance()->addInfo('Created product ' . $productData[self::TITLE_COLUMN]); return $this->dispatchProductEvent(new ProductUpdateEvent($newProduct->getId()), $productData, $locale, $category, $country); } diff --git a/templates/backOffice/default/module-configuration.html b/templates/backOffice/default/module-configuration.html new file mode 100644 index 0000000..c41c53f --- /dev/null +++ b/templates/backOffice/default/module-configuration.html @@ -0,0 +1,50 @@ +{default_translation_domain domain='freedelivery.bo.default'} +
+
+
+ {intl l='Import CSV du catalogue'} +
+
+ +
+
+ {if $smarty.get.error|default:false} +
+ {$smarty.get.error} +
+ {/if} +
+ Pour importer le catalogue de produits CSV, cliquez le bouton ci-dessous. Les fichiers sont + recherchés dans le répertoire FTP "Catalogue". +
+ + {intl l="Lancer l'importation"} + +
+
+ + {if $smarty.get.done|default:false} +
+
+
+
+

+ + {intl l="Log de l'importation"} +

+
+ +
+
+ {$trace_content nofilter} +
+
+ +
+
+
+ {/if} +
+{default_translation_domain domain='bo.default'}