Skip to content

Commit

Permalink
Changed def Cron execution time; Added ProductType attr handler. xml …
Browse files Browse the repository at this point in the history
…generation improvements.
  • Loading branch information
Denis Slepnev committed Sep 20, 2022
1 parent d002661 commit b063d85
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 21 deletions.
34 changes: 27 additions & 7 deletions Converter/ArrayToXmlConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -56,21 +57,40 @@ private function getItem(array $row): string
<g:availability><![CDATA[{$row['is_in_stock']}]]></g:availability>
<g:product_type><![CDATA[{$row['product_type']}]]></g:product_type>
<g:price><![CDATA[{$row['price']}]]></g:price>
<g:brand><![CDATA[{$row['manufacturer']}]]></g:brand>
<g:gtin><![CDATA[{$row['ean']}]]></g:gtin>
<g:mpn><![CDATA[{$row['sku']}]]></g:mpn>
<g:color><![CDATA[{$row['color']}]]></g:color>
<g:gender><![CDATA[{$row['gender']}]]></g:gender>
<g:material><![CDATA[{$row['material']}]]></g:material>
<g:pattern><![CDATA[{$row['pattern']}]]></g:pattern>
{$this->getOptionalProductAttr($row, 'manufacturer', 'brand')}
<g:item_group_id><![CDATA[{$row['item_group_id']}]]></g:item_group_id>
{$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'])}
</item>
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 "<g:$tagKey><![CDATA[$row[$attrKey]]]></g:$tagKey>";
}
return '';
}

private function getAdditionalImageLinks(array $imageLinks): string
{
$result = '';
Expand Down
68 changes: 68 additions & 0 deletions DataProvider/AttributeHandlers/ProductTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers;

use Magento\Catalog\Model\Product;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;

class ProductTypeProvider implements AttributeHandlerInterface
{
/**
* @var CategoryRepositoryInterface
*/
private CategoryRepositoryInterface $categoryRepository;

private array $cache = [];

/**
* @param CategoryRepositoryInterface $categoryRepository
*/
public function __construct(CategoryRepositoryInterface $categoryRepository)
{
$this->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 '';
}
}
}
5 changes: 5 additions & 0 deletions Enum/AttributesToImportEnumInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ 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
```

## Features

### 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
Expand Down Expand Up @@ -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

Expand Down
7 changes: 2 additions & 5 deletions Test/Unit/Converter/ArrayToXmlConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,11 @@ private function getExpectedResult(): string
<g:availability><![CDATA[in_stock]]></g:availability>
<g:product_type><![CDATA[Duo- Bettdecke]]></g:product_type>
<g:price><![CDATA[230,00 EUR]]></g:price>
<g:mpn><![CDATA[FAN-53KBBW01V0011-155/220]]></g:mpn>
<g:brand><![CDATA[f.a.n. Frankenstolz]]></g:brand>
<g:item_group_id><![CDATA[FAN-53KBBW01V0011]]></g:item_group_id>
<g:gtin><![CDATA[4000863525821]]></g:gtin>
<g:mpn><![CDATA[FAN-53KBBW01V0011-155/220]]></g:mpn>
<g:color><![CDATA[]]></g:color>
<g:gender><![CDATA[]]></g:gender>
<g:material><![CDATA[Kaschmir]]></g:material>
<g:pattern><![CDATA[]]></g:pattern>
<g:item_group_id><![CDATA[FAN-53KBBW01V0011]]></g:item_group_id>
<g:product_detail>
<g:section_name><![CDATA[General]]></g:section_name>
<g:attribute_name><![CDATA[Material Stoff]]></g:attribute_name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace RunAsRoot\GoogleShoppingFeed\Test\Unit\DataProvider\AttributeHandlers;

use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Category;
use Magento\Framework\Exception\NoSuchEntityException;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\ProductTypeProvider;

final class ProductTypeProviderTest extends TestCase
{
/**
* @var CategoryRepositoryInterface|MockObject
*/
private CategoryRepositoryInterface $categoryRepositoryMock;

/**
* @var Product|MockObject
*/
private Product $productMock;

private ProductTypeProvider $sut;

protected function setUp(): void
{
$this->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));
}
}
2 changes: 1 addition & 1 deletion etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
showInWebsite="1"
canRestore="1">
<label>Cron Schedule</label>
<comment>By default cron will run every 15 minutes.</comment>
<comment>By default cron will run every 2 hours.</comment>
<source_model>RunAsRoot\GoogleShoppingFeed\SourceModel\FrequencySourceModel</source_model>
<depends>
<field id="run_as_root_product_feed/general/enabled">1</field>
Expand Down
2 changes: 1 addition & 1 deletion etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<default>
<run_as_root_product_feed>
<general>
<cron_schedule>*/15 * * * *</cron_schedule>
<cron_schedule>0 */2 * * *</cron_schedule>
</general>
</run_as_root_product_feed>
</default>
Expand Down
4 changes: 2 additions & 2 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
<argument name="attributeCode" xsi:type="string">pattern</argument>
</arguments>
</virtualType>
<virtualType name="RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\ProductTypeProvider"
<virtualType name="RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\SizeProvider"
type="RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\SelectAttributeHandler">
<arguments>
<argument name="attributeCode" xsi:type="string">product_type</argument>
<argument name="attributeCode" xsi:type="string">size</argument>
</arguments>
</virtualType>
<virtualType name="RunAsRoot\GoogleShoppingFeed\DataProvider\AttributeHandlers\GenderProvider"
Expand Down

0 comments on commit b063d85

Please sign in to comment.