![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/app/code/Cnc/Catalog/Console/ |
<?php /** * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * @author Radosław Łańcucki <[email protected]> * @copyright Copyright (c) 2020 Kaliop Digital Commerce (https://digitalcommerce.kaliop.com) */ declare(strict_types=1); namespace Cnc\Catalog\Console; use Cnc\Catalog\Helper\Data as CncCatalogHelper; use Cnc\Catalog\Model\Product\ImportFactory; use Cnc\Catalog\Model\ResourceModel\DataFactory as SourceDataFactory; use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Setup\SampleData\FixtureManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class ImportImages extends Command { private const ARGUMENT_IMAGE_IMPORT_CATEGORIES = 'categories'; private const ARGUMENT_IMAGE_IMPORT_PRODUCTS = 'products'; private const IMPORT_FILE_PATH_CATEGORY_IMAGES = 'Cnc_Catalog::fixtures/category_images.csv'; private const IMPORT_FILE_PATH_PRODUCT_IMAGES = 'Cnc_Catalog::fixtures/products.csv'; private const CSV_DELIMITER = ','; private const FILE_IMPORT_PATH = 'pub/media/import'; private const FILE_IMPORT_SUB_FOLDER_PATH = 'pub/media/import/diaporama'; private const FILE_MEDIA_PATH_CATEGORY = 'pub/media/catalog/category'; private const CATEGORY_MEDIA_ATTRIBUTE = [ 'image', 'small_image', 'thumbnail' ]; /** * Characters that MUST be removed from Image name */ private const IMAGE_NAME_SPECIAL_CHARACTERS = [ ' ', '°', '+', '(', ')', ';', '#', '$', '&' ]; /** * @var CategoryRepositoryInterface */ private $categoryRepository; /** * @var CategoryCollectionFactory */ private $categoryCollectionFactory; /** * @var ProductCollectionFactory */ private $productCollectionFactory; /** * @var SourceDataFactory */ private $sourceDataFactory; /** * @var ImportFactory */ private $importFactory; /** * @var File */ private $file; /** * @var FixtureManager */ private $fixtureManager; /** * @var ResourceConnection */ private $resourceConnection; /** * @var CncCatalogHelper */ private $cncCatalogHelper; /** * @var array */ private $imageSubFolders = []; /** * ImportImages constructor. * @param CategoryCollectionFactory $categoryCollectionFactory * @param ProductCollectionFactory $productCollectionFactory * @param CategoryRepositoryInterface $categoryRepository * @param File $file * @param FixtureManager $fixtureManager * @param SourceDataFactory $sourceDataFactory * @param ImportFactory $importFactory * @param ResourceConnection $resourceConnection * @param CncCatalogHelper $cncCatalogHelper * @param string|null $name */ public function __construct( CategoryCollectionFactory $categoryCollectionFactory, ProductCollectionFactory $productCollectionFactory, CategoryRepositoryInterface $categoryRepository, File $file, FixtureManager $fixtureManager, SourceDataFactory $sourceDataFactory, ImportFactory $importFactory, ResourceConnection $resourceConnection, CncCatalogHelper $cncCatalogHelper, string $name = null ) { parent::__construct($name); $this->categoryCollectionFactory = $categoryCollectionFactory; $this->productCollectionFactory = $productCollectionFactory; $this->categoryRepository = $categoryRepository; $this->file = $file; $this->fixtureManager = $fixtureManager; $this->sourceDataFactory = $sourceDataFactory; $this->importFactory = $importFactory; $this->resourceConnection = $resourceConnection; $this->cncCatalogHelper = $cncCatalogHelper; } /** * @inheritDoc */ protected function configure() { $this->setName('catalog:image:import'); $this->setDescription('Products/Categories image import'); $this->addOption( self::ARGUMENT_IMAGE_IMPORT_CATEGORIES, 'c', InputOption::VALUE_OPTIONAL, 'Category Images import', [] ); $this->addOption( self::ARGUMENT_IMAGE_IMPORT_PRODUCTS, 'p', InputOption::VALUE_OPTIONAL, 'Category Images import', [] ); parent::configure(); } /** * @param InputInterface $input * @param OutputInterface $output * @return int|void|null * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\FileSystemException * @throws \Magento\Framework\Exception\LocalizedException */ protected function execute(InputInterface $input, OutputInterface $output) { $isCategoriesImport = $input->getOption(self::ARGUMENT_IMAGE_IMPORT_CATEGORIES); $isProductsImport = $input->getOption(self::ARGUMENT_IMAGE_IMPORT_PRODUCTS); if ($isCategoriesImport) { $output->writeln("<info>Started for Category images</info>"); $this->importCategoryImages($output); $output->writeln("<info>Finished for Category images</info>"); } if ($isProductsImport) { $output->writeln("<info>Started for Product images</info>"); $this->importProductImages($output); $output->writeln("<info>Finished for Product images</info>"); } if (!$isCategoriesImport && !$isProductsImport) { $output->writeln( "<info>Please type -categories or -products (or both) option(s) to process images</info>" ); } $output->writeln("<info>Finished Import images</info>"); } /** * @param OutputInterface $output * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\FileSystemException * @throws \Magento\Framework\Exception\LocalizedException */ private function importCategoryImages(OutputInterface $output): void { $categoriesData = $this->loadImportFile(self::IMPORT_FILE_PATH_CATEGORY_IMAGES); $categoryIds = array_column($categoriesData, 'categories_id'); /** @var Category[] $categories */ $categories = $this->getCategories($categoryIds); foreach ($categoriesData as $categoryData) { $categoryId = $categoryData['categories_id']; $image = $categoryData['categories_image'] ?? null; //Do not process not imported Category if (!isset($categories[$categoryId])) { $output->writeln("<info>Category {$categoryId} is not imported!</info>"); continue; } if (!is_string($image)) { $output->writeln("<info>Category {$categoryId} no image detected!</info>"); continue; } if (!$this->copyFile($image, self::FILE_MEDIA_PATH_CATEGORY)) { $output->writeln("<info>Category {$categoryId} image {$image} cannot be copied!</info>"); continue; } /** @var Category $category */ $category = $categories[$categoryId]; $category->setImage($image, self::CATEGORY_MEDIA_ATTRIBUTE, true, false); $category->setStoreId(0); $this->categoryRepository->save($category); } } /** * @param string $filename * @return array * @throws \Magento\Framework\Exception\FileSystemException * @throws \Magento\Framework\Exception\LocalizedException */ private function loadImportFile(string $filename): array { $data = []; $fileName = $this->fixtureManager->getFixture($filename); $file = $this->file->fileOpen($fileName, "r"); $header = $this->file->fileGetCsv($file, 0, self::CSV_DELIMITER); while ($row = $this->file->fileGetCsv($file, 0, self::CSV_DELIMITER)) { $data[] = array_filter(array_combine($header, $row)); } return $data; } /** * @param string $filename * @param string $destination * @return bool * @throws \Magento\Framework\Exception\FileSystemException */ private function copyFile(string $filename, string $destination) { $filePath = $this->cncCatalogHelper->getRealPathCaseInsensitive( self::FILE_IMPORT_PATH . DIRECTORY_SEPARATOR . $filename ); $destination = $this->file->getRealPath($destination) . DIRECTORY_SEPARATOR . $filename; if (!$this->file->isExists($filePath)) { return false; } $this->file->copy($filePath, $destination); return true; } /** * @param array $categoryIds * @return array * @throws \Magento\Framework\Exception\LocalizedException */ private function getCategories(array $categoryIds): array { /** @var CategoryCollection $collection */ $collection = $this->categoryCollectionFactory->create(); $collection->addAttributeToFilter( \Cnc\Catalog\Model\Config::CATEGORY_ATTRIBUTE_CODE_OSCOMMERCE_CAT_ID, ['in' => $categoryIds] ); $items = []; foreach ($collection->getItems() as $category) { $categoryId = $category->getData(\Cnc\Catalog\Model\Config::CATEGORY_ATTRIBUTE_CODE_OSCOMMERCE_CAT_ID); $items[$categoryId] = $category; } return $items; } /** * @param OutputInterface $output * @throws \Magento\Framework\Exception\FileSystemException * @throws \Magento\Framework\Exception\LocalizedException */ private function importProductImages(OutputInterface $output): void { $importedProducts = $this->loadImportFile(self::IMPORT_FILE_PATH_PRODUCT_IMAGES); $currentProducts = $this->getProducts([], true); $data = $this->getProductsImportData($output, $importedProducts, $currentProducts); if (!empty($data)) { $this->cleanProductImages($data); } $sourceModel = $this->sourceDataFactory->create(['data' => [$data]]); $product = $this->importFactory->create(['importData' => $sourceModel]); try { $product->importData(); } catch (\Exception $exception) { var_export($exception->getMessage()); } } /** * @param array $importData */ private function cleanProductImages(array $importData): void { $skus = array_keys($importData); $select = $this->resourceConnection->getConnection()->select() ->from( ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')] ) ->joinLeft( ['val' => $this->resourceConnection->getTableName('catalog_product_entity_media_gallery_value')], 'val.entity_id = cpe.entity_id' ) ->joinLeft( ['gal' => $this->resourceConnection->getTableName('catalog_product_entity_media_gallery')], 'gal.value_id = val.value_id' ) ->where('cpe.sku IN (?)', $skus) ->where('gal.media_type != ?', "external-video") ->reset(\Zend_Db_Select::COLUMNS) ->columns([ 'value_id' => 'val.value_id' ]); $valueIds = $this->resourceConnection->getConnection()->fetchAll($select); if (!empty($valueIds)) { $connection = $this->resourceConnection->getConnection(); $connection->delete( $this->resourceConnection->getTableName('catalog_product_entity_media_gallery'), ['value_id IN (?)' => array_column($valueIds, 'value_id')] ); } } /** * @param array $skus * @param bool $asArray * @return array */ private function getProducts(array $skus, bool $asArray = false): array { /** @var ProductCollection $collection */ $collection = $this->productCollectionFactory->create(); if (!empty($skus)) { $collection->addFieldToFilter('sku', ['in' => $skus]); } if ($asArray) { $select = $collection->getSelect(); $select->joinLeft( ['attribute_set' => $collection->getConnection()->getTableName('eav_attribute_set')], 'e.attribute_set_id = attribute_set.attribute_set_id', ['attribute_set.attribute_set_name'] ); $select->reset('columns') ->columns(['sku', 'entity_id', 'type_id', 'attribute_set.attribute_set_name']); return $collection->getConnection()->fetchAssoc($select); } return $collection->getItems(); } /** * @param OutputInterface $output * @param array $importedData * @param array $currentProducts * @return array */ private function getProductsImportData(OutputInterface $output, array $importedData, array $currentProducts): array { $data = []; $this->imageSubFolders = $this->getImageSubFolders(); foreach ($importedData as $product) { $simpleSku = $product['products_model']; $groupedSku = $product['products_group'] ?? ''; $image = $product['products_image'] ?? null; //Do not import images for not imported Product if (!isset($currentProducts[$simpleSku])) { $output->writeln("<info>Product {$simpleSku} is not imported!</info>"); continue; } if (is_string($image)) { $image = $this->getValidatedImageName(self::FILE_IMPORT_PATH, $image); } else { $output->writeln("<info>Product {$simpleSku} main image is not defined!</info>"); } $additionalImages = $this->getAdditionalImages($simpleSku); $data[$simpleSku] = $this->addProductImportData( $simpleSku, $image, $additionalImages, $currentProducts[$simpleSku]['attribute_set_name'], $currentProducts[$simpleSku]['type_id'] ); //Check potential grouped Product if (!empty($groupedSku)) { //Additional condition to change grouped sku when products.csv file have same sku for simple and grouped if (!isset($currentProducts[$groupedSku]) || $currentProducts[$groupedSku]['type_id'] === 'simple') { $groupedSku = $groupedSku . '-g'; } if (isset($currentProducts[$groupedSku]) && $currentProducts[$groupedSku]['type_id'] === 'grouped' && isset($product['products_order']) && $product['products_order'] == 1) { $data[$groupedSku] = $this->addProductImportData( $groupedSku, $image, $additionalImages, $currentProducts[$groupedSku]['attribute_set_name'], $currentProducts[$groupedSku]['type_id'] ); } } } return $data; } /** * @param string $sku * @param string $image * @param string $additionalImages * @param string $attributeSet * @param string $type * @return array */ private function addProductImportData( string $sku, $image, string $additionalImages, string $attributeSet, string $type ): array { $data = [ 'sku' => $sku, 'image' => $image, 'small_image' => $image, 'thumbnail' => $image, 'product_type' => $type, '_attribute_set' => $attributeSet ]; if (!empty($additionalImages)) { $data['_media_image'] = $additionalImages; } return $data; } /** * @param string $path * @param string $image * @return string * @throws \Magento\Framework\Exception\FileSystemException */ private function getValidatedImageName(string $path, string &$image): string { $caseSensitiveFixedPath = $this->cncCatalogHelper->getRealPathCaseInsensitive( $path . DIRECTORY_SEPARATOR . $image ); if ($caseSensitiveFixedPath) { $pathAsArray = explode(DIRECTORY_SEPARATOR, $caseSensitiveFixedPath); //Set fixed name of image file because of problems with letters size. $image = end($pathAsArray); } foreach (self::IMAGE_NAME_SPECIAL_CHARACTERS as $specialCharacter) { if (strpos($image, $specialCharacter) !== false) { $image = $this->getFixedImageFile($path, $image, $specialCharacter); } } return $image; } /** * @param string $path * @param string $image * @param string $specialCharacter * @return string * @throws \Magento\Framework\Exception\FileSystemException */ private function getFixedImageFile(string $path, string $image, string $specialCharacter): string { $oldPath = $this->file->getRealPath($path . DIRECTORY_SEPARATOR . $image); $newImage = str_replace($specialCharacter, '', $image); $newPath = $this->file->getRealPath($path) . DIRECTORY_SEPARATOR . $newImage; if ($this->file->isExists($oldPath)) { $this->file->rename($oldPath, $newPath); } return $newImage; } /** * @param string $path * @return string */ private function getFixedImagePath(string $path): string { $newPath = $path; foreach (self::IMAGE_NAME_SPECIAL_CHARACTERS as $specialCharacter) { if (strpos($path, $specialCharacter) !== false) { $newPath = str_replace($specialCharacter, '', $path); } } //We need to create New Path and copy all files from old Path in case of data normalization if ($newPath !== $path) { if (!$this->file->isDirectory($newPath)) { $this->file->createDirectory($newPath); $files = $this->file->readDirectory($path); foreach ($files as $file) { $newFile = str_replace($path, $newPath, $file); $this->file->copy($file, $newFile); } } } return $newPath; } /** * @return array */ private function getImageSubFolders(): array { $list = []; foreach ($this->file->readDirectory(self::FILE_IMPORT_SUB_FOLDER_PATH) as $dirPath) { $dirName = explode('/', $dirPath); $sku = end($dirName); $list[$sku] = $sku; } return $list; } /** * @param string $sku * @return string * @throws \Magento\Framework\Exception\FileSystemException */ private function getAdditionalImages(string $sku): string { $additionalImages = []; if (isset($this->imageSubFolders[$sku])) { $dirPath = self::FILE_IMPORT_SUB_FOLDER_PATH . DIRECTORY_SEPARATOR . $sku; $dirPath = $this->getFixedImagePath($dirPath); if ($this->file->isDirectory($dirPath)) { $files = $this->file->readDirectory($dirPath); foreach ($files as $file) { if ($this->file->isFile($file)) { $parts = explode('/', $file); $file = end($parts); $image = $this->getValidatedImageName($dirPath, $file); //Importer requires relative path starting from main import Media folder $additionalImages[] = str_replace(self::FILE_IMPORT_PATH . DIRECTORY_SEPARATOR, '', $dirPath) . DIRECTORY_SEPARATOR . $image; } } } } return implode(',', $additionalImages); } }