diff --git a/src/Divante/MagentoIntegrationBundle/Action/Mapper/ExportAction.php b/src/Divante/MagentoIntegrationBundle/Action/Mapper/ExportAction.php new file mode 100644 index 0000000..4363b50 --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Action/Mapper/ExportAction.php @@ -0,0 +1,48 @@ +domain = $domain; + $this->responder = $jsonFileResponder; + } + + /** + * @param Request $query + * @return JsonResponse + * @throws \Exception + */ + public function __invoke(Request $query): Response + { + return $this->responder->createResponse( + sprintf("export_%s_mapping", $query->get('type')), + $this->domain->getExportMappingData($query->get("id"), $query->get('type')) + ); + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Action/Mapper/ImportAction.php b/src/Divante/MagentoIntegrationBundle/Action/Mapper/ImportAction.php new file mode 100644 index 0000000..033b970 --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Action/Mapper/ImportAction.php @@ -0,0 +1,62 @@ +domain = $domain; + $this->responder = $jsonResponder; + } + + /** + * @param Request $query + * @return JsonResponse + */ + public function __invoke(Request $query): JsonResponse + { + try { + $this->domain->importMappingData( + $query->get('id'), + $query->get('type'), + $query->files->get('file') + ); + return $this->responder->createResponse( + [ + "success" => true, + ] + ); + } catch (MappingImportException $exception) { + return $this->responder->createResponse( + [ + "success" => false, + "message" => $exception->getMessage() + ] + ); + } + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperExporter.php b/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperExporter.php new file mode 100644 index 0000000..e1efd2e --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperExporter.php @@ -0,0 +1,56 @@ +exporterHelper = $exporterHelper; + } + + /** + * @param int $idConfig + * @param string $type + * @return array + * @throws MappingExportException + */ + public function getExportMappingData(int $idConfig, string $type): array + { + $config = IntegrationConfiguration::getById($idConfig); + if (!$config instanceof IntegrationConfiguration) { + throw new MappingExportException("Configuration not found!"); + } + + switch ($type) { + case ObjectTypeHelper::PRODUCT: + $mapping = $config->getProductMapping(); + break; + case ObjectTypeHelper::CATEGORY: + $mapping = $config->getCategoryMapping(); + break; + default: + throw new MappingExportException("Invalid type provided: " . $type); + } + + return $this->exporterHelper->buildFileMapping($mapping, $type); + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperImporter.php b/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperImporter.php new file mode 100644 index 0000000..c46acbd --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Application/Mapper/MapperImporter.php @@ -0,0 +1,74 @@ +importerHelper = $importerHelper; + } + + /** + * @param string $idConfig + * @param string $type + * @param UploadedFile $file + * @return void + * @throws MappingImportException + */ + public function importMappingData(string $idConfig, string $type, UploadedFile $file): void + { + $config = IntegrationConfiguration::getById($idConfig); + if (!$config instanceof IntegrationConfiguration) { + throw new MappingImportException("Configuration not found!"); + } + + $mapping = $this->importerHelper->extractFileMapping($file); + if ($type !== $mapping->getType()) { + throw new MappingImportException( + sprintf( + "Wrong file, type is set to '%s' and should be '%s'", + $mapping->getType(), + $type + ) + ); + } + + switch ($mapping->getType()) { + case ObjectTypeHelper::PRODUCT: + $config->setProductMapping($mapping->getData()); + break; + case ObjectTypeHelper::CATEGORY: + $config->setCategoryMapping($mapping->getData()); + break; + default: + throw new MappingImportException("Invalid type provided: " . $mapping->getType()); + } + + try { + $config->setOmitMandatoryCheck(true)->save(); + } catch (\Exception $exception) { + throw new MappingImportException($exception->getMessage()); + } + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Application/Product/MappedProductService.php b/src/Divante/MagentoIntegrationBundle/Application/Product/MappedProductService.php index e801a29..b4ee197 100644 --- a/src/Divante/MagentoIntegrationBundle/Application/Product/MappedProductService.php +++ b/src/Divante/MagentoIntegrationBundle/Application/Product/MappedProductService.php @@ -9,8 +9,8 @@ namespace Divante\MagentoIntegrationBundle\Application\Product; use Divante\MagentoIntegrationBundle\Application\Common\AbstractMappedObjectService; -use Divante\MagentoIntegrationBundle\Domain\Event\IntegratedObjectEvent; -use Divante\MagentoIntegrationBundle\Domain\Event\PostMappingObjectEvent; +use Divante\MagentoIntegrationBundle\Domain\Common\Event\IntegratedObjectEvent; +use Divante\MagentoIntegrationBundle\Domain\Common\Event\PostMappingObjectEvent; use Divante\MagentoIntegrationBundle\Domain\IntegrationConfiguration\IntegrationHelper; use Divante\MagentoIntegrationBundle\Domain\Mapper\MapperEventTypes; use Divante\MagentoIntegrationBundle\Domain\DataObject\IntegrationConfiguration; diff --git a/src/Divante/MagentoIntegrationBundle/Command/ImportMappingCommand.php b/src/Divante/MagentoIntegrationBundle/Command/ImportMappingCommand.php new file mode 100644 index 0000000..864c2d2 --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Command/ImportMappingCommand.php @@ -0,0 +1,93 @@ +mapperImporter = $mapperImporter; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setDescription('Import product or category mappings to selected configuration object'); + + $this->addArgument( + "idConfiguration", + InputArgument::REQUIRED, + "Id of selected integration configuration, you want to import mappings" + ); + + $this->addArgument( + "type", + InputArgument::REQUIRED, + "Type 'product' or 'category' mapping" + ); + + $this->addArgument( + "path", + InputArgument::REQUIRED, + "Path to json file" + ); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $start = microtime(true); + $idConfig = $input->getArgument("idConfiguration"); + $type = $input->getArgument("type"); + $filePath = $input->getArgument("path"); + + $file = new UploadedFile($filePath, "mappings"); + try { + $this->mapperImporter->importMappingData($idConfig, $type, $file); + $output->writeln("Import Succeed!"); + } catch (\Exception $exception) { + $errorMsg = $exception->getMessage(); + $output->writeln("Import Failure!"); + $output->writeln("Message: " . $errorMsg . ""); + } + + $timeElapsed = microtime(true) - $start; + + $output->writeln(sprintf("Execution time : %.2f seconds", $timeElapsed)); + + return 0; + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Command/SendCategoriesCommand.php b/src/Divante/MagentoIntegrationBundle/Command/SendCategoriesCommand.php index d727da0..0673647 100644 --- a/src/Divante/MagentoIntegrationBundle/Command/SendCategoriesCommand.php +++ b/src/Divante/MagentoIntegrationBundle/Command/SendCategoriesCommand.php @@ -16,7 +16,7 @@ class SendCategoriesCommand extends AbstractCommand { - private $updateSerice; + private $updateService; protected static $defaultName = 'integration-magento:send:category'; /** @@ -27,7 +27,7 @@ class SendCategoriesCommand extends AbstractCommand public function __construct(BulkUpdateService $bulkUpdateService, string $name = null) { parent::__construct($name); - $this->updateSerice = $bulkUpdateService; + $this->updateService = $bulkUpdateService; } /** @@ -40,7 +40,7 @@ protected function configure() $this->addArgument( "idCategory", InputArgument::REQUIRED, - "Id or comma separated ids of produ180cts you want to send or 'all' if you want to send all of them" + "Id or comma separated ids of products you want to send or 'all' if you want to send all of them" ); $this->addArgument( @@ -60,8 +60,8 @@ public function execute(InputInterface $input, OutputInterface $output): int $start = microtime(true); $idCategory = $input->getArgument("idCategory"); $idConfig = $input->getArgument("idConfiguration"); - $this->updateSerice->setLogger(new ConsoleLogger($output)); - $objects = $this->updateSerice->updateCategories($idCategory, $idConfig); + $this->updateService->setLogger(new ConsoleLogger($output)); + $objects = $this->updateService->updateCategories($idCategory, $idConfig); $timeElapsed = microtime(true) - $start; $output->writeln("Send Categories command has succeeded."); diff --git a/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Exception/MappingExportException.php b/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Exception/MappingExportException.php new file mode 100644 index 0000000..5893c87 --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Exception/MappingExportException.php @@ -0,0 +1,11 @@ +getMimeType() !== "application/json") { + throw new MappingImportException("Invalid file format!"); + } + + try { + $fileData = json_decode(file_get_contents($file), 1); + } catch (\Exception $exception) { + throw new MappingImportException($exception->getMessage()); + } + + $type = $this->extractType($fileData); + $data = $this->extractMappings($fileData, $type); + + $mapping = new Mapping(); + $mapping->setType($type); + $mapping->setData($data); + + return $mapping; + } + + /** + * @param array $mappings + * @param string $type + * @return array + */ + public function buildFileMapping(array $mappings, string $type): array + { + $formatted = []; + $formatted["type"] = $type; + $formatted["data"] = $this->formatData($mappings, $type); + + return $formatted; + } + + /** + * @param array $mappings + * @param string $type + * @return array + */ + protected function formatData(array $mappings, string $type): array + { + $formatted = []; + foreach ($mappings as $mapping) { + $baseFields = [ + "from" => $mapping[0], + "to" => $mapping[1], + "strategy" => $mapping[2], + "relation_fields" => $mapping[3], + "thumbnail" => $mapping[4], + ]; + if ($type === ObjectTypeHelper::PRODUCT) { + $configFields = [ + "searchable" => !is_bool($mapping[5]) ? false : $mapping[5], + "filterable" => !is_bool($mapping[6]) ? false : $mapping[6], + "comparable" => !is_bool($mapping[7]) ? false : $mapping[7], + "visible" => !is_bool($mapping[8]) ? false : $mapping[8], + "used_in_product" => !is_bool($mapping[9]) ? false : $mapping[9], + ]; + } + $formatted[] = array_merge($baseFields, $configFields ?? []); + } + + return $formatted; + } + + /** + * @param array $data + * @return string + * @throws MappingImportException + */ + protected function extractType(array $data): string + { + if (!array_key_exists("type", $data)) { + throw new MappingImportException("File is corrupted, no information about type provided"); + } + + return $data['type']; + } + + /** + * @param array $data + * @param string $type + * @return array + */ + protected function extractMappings(array $data, string $type): array + { + $extracted = []; + foreach ($data["data"] as $datum) { + $baseFields = [ + $datum["from"], + $datum["to"], + $datum["strategy"], + $datum["relation_fields"], + $datum["thumbnail"] + ]; + if ($type === ObjectTypeHelper::PRODUCT) { + $configFields = [ + $datum["searchable"], + $datum["filterable"], + $datum["comparable"], + $datum["visible"], + $datum["used_in_product"] + ]; + } + $extracted[] = array_merge($baseFields, $configFields ?? []); + } + + return $extracted; + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Model/Mapping.php b/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Model/Mapping.php new file mode 100644 index 0000000..4e6685c --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Domain/Mapper/Model/Mapping.php @@ -0,0 +1,46 @@ +type; + } + + /** + * @param string $type + */ + public function setType(string $type): void + { + $this->type = $type; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } +} diff --git a/src/Divante/MagentoIntegrationBundle/Resources/config/mapper.yml b/src/Divante/MagentoIntegrationBundle/Resources/config/mapper.yml index ed4f09d..cae4697 100644 --- a/src/Divante/MagentoIntegrationBundle/Resources/config/mapper.yml +++ b/src/Divante/MagentoIntegrationBundle/Resources/config/mapper.yml @@ -20,6 +20,8 @@ services: Divante\MagentoIntegrationBundle\Application\Mapper\MapperContext: ~ Divante\MagentoIntegrationBundle\Application\Mapper\MapperManager: ~ + Divante\MagentoIntegrationBundle\Application\Mapper\MapperExporter: ~ + Divante\MagentoIntegrationBundle\Application\Mapper\MapperImporter: ~ Divante\MagentoIntegrationBundle\Application\Mapper\MapperColumnsService: arguments: diff --git a/src/Divante/MagentoIntegrationBundle/Resources/config/pimcore/routing.yml b/src/Divante/MagentoIntegrationBundle/Resources/config/pimcore/routing.yml index aa1a044..8492174 100644 --- a/src/Divante/MagentoIntegrationBundle/Resources/config/pimcore/routing.yml +++ b/src/Divante/MagentoIntegrationBundle/Resources/config/pimcore/routing.yml @@ -51,4 +51,14 @@ magento_integration_mapper.add-row: magento_integration_mapper.remove-row: resource: "@DivanteMagentoIntegrationBundle/Action/Mapper/RemoveRowAction.php" type: annotation - prefix: /admin \ No newline at end of file + prefix: /admin + +magento_integration_mapper.export: + resource: "@DivanteMagentoIntegrationBundle/Action/Mapper/ExportAction.php" + type: annotation + prefix: /admin + +magento_integration_mapper.import: + resource: "@DivanteMagentoIntegrationBundle/Action/Mapper/ImportAction.php" + prefix: /admin + type: annotation diff --git a/src/Divante/MagentoIntegrationBundle/Resources/config/services.yml b/src/Divante/MagentoIntegrationBundle/Resources/config/services.yml index de7ebd9..f5ffe6e 100755 --- a/src/Divante/MagentoIntegrationBundle/Resources/config/services.yml +++ b/src/Divante/MagentoIntegrationBundle/Resources/config/services.yml @@ -86,6 +86,8 @@ services: class: Divante\MagentoIntegrationBundle\Domain\IntegrationConfiguration\PathCalculator public: true + Divante\MagentoIntegrationBundle\Domain\Mapper\ImporterExporterHelper: ~ + ### Infrastructure Divante\MagentoIntegrationBundle\Infrastructure\IntegrationConfiguration\IntegrationConfigurationListener: tags: @@ -146,6 +148,7 @@ services: # Responder Divante\MagentoIntegrationBundle\Responder\JsonResponder: ~ + Divante\MagentoIntegrationBundle\Responder\JsonFileResponder: ~ Divante\MagentoIntegrationBundle\Responder\MappedObjectJsonResponder: ~ #Monolog diff --git a/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/categoryMapper.js b/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/categoryMapper.js index 3c29553..b5d221b 100644 --- a/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/categoryMapper.js +++ b/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/categoryMapper.js @@ -164,6 +164,7 @@ pimcore.plugin.MagentoIntegrationBundle.CategoryMapper = Class.create(pimcore.pl valueField: 'identifier', object: this.object, editable: true, + minChars: 1, listeners: { focus: function (comp, record, index) { if (comp.getValue() === "" || comp.getValue() === "(Empty)") { @@ -468,11 +469,73 @@ pimcore.plugin.MagentoIntegrationBundle.CategoryMapper = Class.create(pimcore.pl }.bind(this) }); + this.exportMapping = new Ext.Button({ + iconCls: 'pimcore_icon_download', + text: '' + t("Export mapping") + '', + tooltip: t("Export current mapping configuration"), + href: "/admin/mappings/export/category/" + this.object.id + }); + + this.importMapping = new Ext.Button({ + iconCls: 'pimcore_icon_upload', + text: '' + t("Import mapping") + '', + tooltip: t("Import mapping configuration"), + handler: function () { + var popup = Ext.create('Ext.window.Window', { + title: 'Upload category mapping', + height: 150, + width: 500, + layout: 'fit', + items: [ + new Ext.form.FormPanel({ + width: 400, + bodyPadding: 10, + frame: true, + url: '/admin/mappings/import/category/' + this.object.id, + renderTo: Ext.getBody(), + items: [{ + xtype: 'filefield', + msgTarget: 'side', + allowBlank: false, + name: 'file', + anchor: '100%', + buttonText: 'Browse' + }], + buttons: [{ + text: 'Upload', + handler: function(bt) { + var form = bt.up('form').getForm(); + if(form.isValid()) { + form.submit({ + params: { + csrfToken: pimcore.settings['csrfToken'] + }, + waitMsg: 'Uploading your configuration', + success: function(fp, o) { + Ext.Msg.alert('Success', 'Your configuration has been uploaded.'); + popup.close(); + }, + failure: function(fp, o){ + var response = JSON.parse(this.response.responseText); + Ext.Msg.alert('Error', response.message); + } + }); + this.object.reload(); + } + }.bind(this) + }] + }) + ]}).show(); + }.bind(this) + }); + return new Ext.Toolbar({ scrollable: "x", items: [ this.addRow, "-", - this.deleteRow, "-", + this.deleteRow, "->", + this.exportMapping, "-", + this.importMapping ] }); }, diff --git a/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/productMapper.js b/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/productMapper.js index c8159d2..09df3d3 100644 --- a/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/productMapper.js +++ b/src/Divante/MagentoIntegrationBundle/Resources/public/js/pimcore/productMapper.js @@ -161,6 +161,7 @@ pimcore.plugin.MagentoIntegrationBundle.ProductMapper = Class.create(pimcore.plu valueField: 'identifier', object: this.object, editable: true, + minChars: 1, listeners: { focus: function (comp, record, index) { if (comp.getValue() === "" || comp.getValue() === "(Empty)") { @@ -570,11 +571,73 @@ pimcore.plugin.MagentoIntegrationBundle.ProductMapper = Class.create(pimcore.plu }.bind(this) }); + this.exportMapping = new Ext.Button({ + iconCls: 'pimcore_icon_download', + text: '' + t("Export mapping") + '', + tooltip: t("Export current mapping configuration"), + href: "/admin/mappings/export/product/" + this.object.id + }); + + this.importMapping = new Ext.Button({ + iconCls: 'pimcore_icon_upload', + text: '' + t("Import mapping") + '', + tooltip: t("Import mapping configuration"), + handler: function () { + var popup = Ext.create('Ext.window.Window', { + title: 'Upload product mapping', + height: 150, + width: 500, + layout: 'fit', + items: [ + new Ext.form.FormPanel({ + width: 400, + bodyPadding: 10, + frame: true, + url: '/admin/mappings/import/product/' + this.object.id, + renderTo: Ext.getBody(), + items: [{ + xtype: 'filefield', + msgTarget: 'side', + allowBlank: false, + name: 'file', + anchor: '100%', + buttonText: 'Browse' + }], + buttons: [{ + text: 'Upload', + handler: function(bt) { + var form = bt.up('form').getForm(); + if(form.isValid()) { + form.submit({ + params: { + csrfToken: pimcore.settings['csrfToken'] + }, + waitMsg: 'Uploading your configuration', + success: function(fp, o) { + Ext.Msg.alert('Success', 'Your configuration has been uploaded.'); + popup.close(); + }, + failure: function(fp, o){ + var response = JSON.parse(this.response.responseText); + Ext.Msg.alert('Error', response.message); + } + }); + this.object.reload(); + } + }.bind(this) + }] + }) + ]}).show(); + }.bind(this) + }); + return new Ext.Toolbar({ scrollable: "x", items: [ this.addRow, "-", - this.deleteRow, + this.deleteRow, "->", + this.exportMapping, "-", + this.importMapping ] }); }, diff --git a/src/Divante/MagentoIntegrationBundle/Responder/JsonFileResponder.php b/src/Divante/MagentoIntegrationBundle/Responder/JsonFileResponder.php new file mode 100644 index 0000000..e52788d --- /dev/null +++ b/src/Divante/MagentoIntegrationBundle/Responder/JsonFileResponder.php @@ -0,0 +1,29 @@ +headers->set('Content-Type', 'text/json'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename); + $response->setContent(json_encode($data, JSON_PRETTY_PRINT)); + + return $response; + } +}