From b063d85c1c548a1f159c9a1f402c33e52bb0ec5a Mon Sep 17 00:00:00 2001 From: Denis Slepnev Date: Tue, 20 Sep 2022 17:19:05 +0300 Subject: [PATCH] Changed def Cron execution time; Added ProductType attr handler. xml generation improvements. --- Converter/ArrayToXmlConverter.php | 34 ++++++++-- .../AttributeHandlers/ProductTypeProvider.php | 68 +++++++++++++++++++ Enum/AttributesToImportEnumInterface.php | 5 ++ README.md | 10 +-- .../Converter/ArrayToXmlConverterTest.php | 7 +- .../ProductTypeProviderTest.php | 67 ++++++++++++++++++ etc/adminhtml/system.xml | 2 +- etc/config.xml | 2 +- etc/di.xml | 4 +- 9 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 DataProvider/AttributeHandlers/ProductTypeProvider.php create mode 100644 Test/Unit/DataProvider/AttributeHandlers/ProductTypeProviderTest.php diff --git a/Converter/ArrayToXmlConverter.php b/Converter/ArrayToXmlConverter.php index 5c102fe..ba00b13 100644 --- a/Converter/ArrayToXmlConverter.php +++ b/Converter/ArrayToXmlConverter.php @@ -21,7 +21,8 @@ public function convert(array $rows): string $xml .= $this->getCreationTimeTag() . PHP_EOL; foreach ($rows as $row) { - $xml .= $this->getItem($row) . PHP_EOL; + $pattern = "/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/"; + $xml .= preg_replace($pattern, "\n", $this->getItem($row)) . PHP_EOL; } $xml .= $this->getCloseRootTag(); @@ -56,14 +57,15 @@ private function getItem(array $row): string - - - - - - + {$this->getOptionalProductAttr($row, 'manufacturer', 'brand')} + {$this->getOptionalProductAttr($row, 'ean', 'gtin')} + {$this->getOptionalProductAttr($row, 'color')} + {$this->getOptionalProductAttr($row, 'gender')} + {$this->getOptionalProductAttr($row, 'material')} + {$this->getOptionalProductAttr($row, 'size')} + {$this->getOptionalProductAttr($row, 'pattern')} {$this->getProductDetail($row['product_detail'])} {$this->getShipping($row['shipping'])} {$this->getAdditionalImageLinks($row['additional_image_link'])} @@ -71,6 +73,24 @@ private function getItem(array $row): string XML; } + /** + * @param array $row + * @param string $attrKey + * @param string $tagKey + * + * @return string + */ + private function getOptionalProductAttr(array $row, string $attrKey, string $tagKey = ''): string + { + if ($tagKey === '') { + $tagKey = $attrKey; + } + if (!empty($row[$attrKey])) { + return ""; + } + return ''; + } + private function getAdditionalImageLinks(array $imageLinks): string { $result = ''; diff --git a/DataProvider/AttributeHandlers/ProductTypeProvider.php b/DataProvider/AttributeHandlers/ProductTypeProvider.php new file mode 100644 index 0000000..db46be4 --- /dev/null +++ b/DataProvider/AttributeHandlers/ProductTypeProvider.php @@ -0,0 +1,68 @@ +categoryRepository = $categoryRepository; + } + + public function get(Product $product) + { + return $this->getProductType($product); + } + + /** + * @param Product $product + * + * @return string + */ + private function getProductType(Product $product): string + { + $categoryIds = $product->getCategoryIds(); + if (empty($categoryIds)) { + return ''; + } + + $categoryId = (int)$categoryIds[0]; + if (array_key_exists($categoryId, $this->cache)) { + return $this->cache[$categoryId]; + } + + try { + $fullCategoryPath = ''; + $category = $this->categoryRepository->get($categoryId, $product->getStoreId()); + $pathInStore = $category->getPathInStore(); + $pathIds = array_reverse(explode(',', $pathInStore)); + + $categories = $category->getParentCategories(); + + foreach ($pathIds as $categoryId) { + if (isset($categories[$categoryId]) && $categories[$categoryId]->getName()) { + $fullCategoryPath .= $categories[$categoryId]->getName() . ' > '; + } + } + $this->cache[$categoryId] = rtrim($fullCategoryPath, '> '); + return $this->cache[$categoryId]; + + } catch (NoSuchEntityException $noSuchEntityException) { + return ''; + } + } +} \ No newline at end of file diff --git a/Enum/AttributesToImportEnumInterface.php b/Enum/AttributesToImportEnumInterface.php index 1f7544a..1eee410 100644 --- a/Enum/AttributesToImportEnumInterface.php +++ b/Enum/AttributesToImportEnumInterface.php @@ -16,6 +16,7 @@ use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\ItemGroupIdProvider; use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\ManufacturerProvider; use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\MaterialProvider; +use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\SizeProvider; use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\NameProvider; use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\PatternProvider; use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\PriceProvider; @@ -72,6 +73,10 @@ interface AttributesToImportEnumInterface AttributeConfigData::FIELD_NAME => 'material', AttributeConfigData::ATTRIBUTE_HANDLER => MaterialProvider::class, ], + 'size' => [ + AttributeConfigData::FIELD_NAME => 'size', + AttributeConfigData::ATTRIBUTE_HANDLER => SizeProvider::class, + ], 'material_cloth' => [ AttributeConfigData::FIELD_NAME => 'material_cloth', AttributeConfigData::ATTRIBUTE_HANDLER => ManufacturerProvider::class, diff --git a/README.md b/README.md index 39cbf3a..9d05db3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Generate feed with product data. ## Installations ``` -composer require run_as_root/ext-magento2-run_as_root-feed +composer require run_as_root/ext-magento2-google-shopping-feed bin/magento setup:upgrade ``` @@ -12,7 +12,7 @@ bin/magento setup:upgrade ### Feed generation -Generate product feed every 15 minutes with minimal required attributes, for each storeview. +Generate product feed every 2 hours with minimal required attributes, for each storeview. Places file into `pub/run_as_root/feed/%s_store_%s_feed.csv`. ## Technical Specification @@ -51,9 +51,9 @@ See configuration list here: `\RunAsRoot\GoogleShoppingFeed\Enum\AttributesToImp Provides attribute data provider (handler) of type `\RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\AttributeHandlerInterface`, by `AttributeConfigData` DTO. #### `\RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\AttributeHandlerInterface` -Generic interface for attribute data prviders. -Each attrbiute has its own data provider, that incapsulates current interface. -Data provider for spcific attribute is configured here `\RunAsRoot\GoogleShoppingFeed\Enum\AttributesToImportEnumInterface::ATTRIBUTES`. +Generic interface for attribute data providers. +Each attribute has its own data provider, that incapsulates current interface. +Data provider for specific attribute is configured here `\RunAsRoot\GoogleShoppingFeed\Enum\AttributesToImportEnumInterface::ATTRIBUTES`. ### Services diff --git a/Test/Unit/Converter/ArrayToXmlConverterTest.php b/Test/Unit/Converter/ArrayToXmlConverterTest.php index 599e20d..731739f 100644 --- a/Test/Unit/Converter/ArrayToXmlConverterTest.php +++ b/Test/Unit/Converter/ArrayToXmlConverterTest.php @@ -102,14 +102,11 @@ private function getExpectedResult(): string + + - - - - - diff --git a/Test/Unit/DataProvider/AttributeHandlers/ProductTypeProviderTest.php b/Test/Unit/DataProvider/AttributeHandlers/ProductTypeProviderTest.php new file mode 100644 index 0000000..6310118 --- /dev/null +++ b/Test/Unit/DataProvider/AttributeHandlers/ProductTypeProviderTest.php @@ -0,0 +1,67 @@ +categoryRepositoryMock = $this->createMock(CategoryRepositoryInterface::class); + $this->productMock = $this->createMock(Product::class); + + $this->sut = new ProductTypeProvider($this->categoryRepositoryMock); + } + + public function testProductTypeInCaseOfEmptyCategoryIds(): void + { + $this->productMock->method('getCategoryIds')->willReturn([]); + $this->assertEquals('', $this->sut->get($this->productMock)); + } + + public function testProductTypeInCaseOfNonExistingCategory(): void + { + $this->productMock->method('getCategoryIds')->willReturn([1000]); + $this->categoryRepositoryMock->method('get')->willThrowException(new NoSuchEntityException()); + $this->assertEquals('', $this->sut->get($this->productMock)); + } + + public function testProductTypeInCaseOfExistingCategory(): void + { + $this->productMock->method('getCategoryIds')->willReturn([10]); + $category = $this->createMock(Category::class); + + $this->productMock->method('getStoreId')->willReturn(1); + + $this->categoryRepositoryMock->method('get')->with(10)->willReturn($category); + + $category->method('getPathInStore')->willReturn("10,2"); + + $parentCategory = $this->createMock(Category::class); + $category->method('getParentCategories')->willReturn([10 => $category, 2 => $parentCategory]); + + $category->method('getName')->willReturn('Child Category Name'); + $parentCategory->method('getName')->willReturn('Parent Category Name'); + + $this->assertEquals('Parent Category Name > Child Category Name', $this->sut->get($this->productMock)); + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 948a0e5..4eb9d52 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -29,7 +29,7 @@ showInWebsite="1" canRestore="1"> - By default cron will run every 15 minutes. + By default cron will run every 2 hours. RunAsRoot\GoogleShoppingFeed\SourceModel\FrequencySourceModel 1 diff --git a/etc/config.xml b/etc/config.xml index a86a901..6150970 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -3,7 +3,7 @@ - */15 * * * * + 0 */2 * * * diff --git a/etc/di.xml b/etc/di.xml index fb41397..e306199 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -26,10 +26,10 @@ pattern - - product_type + size