From 0c547b53a24fa509695003f41b0bd2b7b74f9b37 Mon Sep 17 00:00:00 2001 From: jekabs Date: Thu, 5 Nov 2020 16:14:28 +0200 Subject: [PATCH 01/13] Product variants -Module and changes to existing infrastructure to support product variants -Added configurable product variants -Added tests for product variants --- .../CatalogDataExporter/etc/et_schema.xml | 12 -- .../Model/ChangedEntitiesMessageBuilder.php | 2 +- .../Model/Indexer/EntityIndexerCallback.php | 18 +- .../Model/ProductVariantRepository.php | 182 +++++++++++++++++ .../Test/Api/ProductVariantExportTest.php | 141 +++++++++++++ app/code/Magento/CatalogExport/composer.json | 1 + .../CatalogExport/etc/communication.xml | 1 + app/code/Magento/CatalogExport/etc/di.xml | 12 ++ app/code/Magento/CatalogExport/etc/module.xml | 1 + .../CatalogExport/etc/queue_publisher.xml | 3 + .../CatalogExport/etc/queue_topology.xml | 1 + app/code/Magento/CatalogExport/etc/webapi.xml | 16 ++ .../Api/Data/ProductVariant.php | 16 +- .../Api/ProductVariantRepositoryInterface.php | 30 +++ ...Uid.php => ConfigurableOptionValueUid.php} | 6 +- .../Model/Provider/Product/Options.php | 16 +- .../Provider/Product/ProductVariants.php | 134 +++++++++++++ .../ProductVariants/ConfigurableId.php | 35 ++++ .../ConfigurableOptionValue.php | 36 ++++ .../Model/Query/ProductVariantsQuery.php | 92 +++++++++ .../Plugin/Query/LinkedAttributesQuery.php | 73 +++++++ .../Plugin/ReindexVariants.php | 84 ++++++++ .../ConfigurableProductVariantsTest.php | 96 +++++++++ .../Integration/ConfigurableProductsTest.php | 11 +- .../composer.json | 1 + .../etc/di.xml | 25 +++ .../etc/et_schema.xml | 1 + app/code/Magento/DataExporter/Model/Feed.php | 10 +- .../DataExporter/Model/FeedInterface.php | 4 +- .../Model/Indexer/DataSerializer.php | 8 +- .../Model/Indexer/FeedIndexMetadata.php | 24 +-- .../Model/Indexer/FeedIndexer.php | 22 +- .../ProductVariantDataExporter/LICENSE.txt | 48 +++++ .../LICENSE_AFL.txt | 48 +++++ .../ProductVariantFeedIndexMetadata.php | 66 ++++++ .../Indexer/ProductVariantFeedIndexer.php | 189 ++++++++++++++++++ .../Model/ProductVariantFeed.php | 66 ++++++ .../Model/ProductVariantFeedInterface.php | 32 +++ .../Model/Provider/ProductVariants.php | 43 ++++ .../Provider/ProductVariants/IdFactory.php | 63 ++++++ .../Provider/ProductVariants/IdInterface.php | 23 +++ .../ProductVariants/OptionValueFactory.php | 63 ++++++ .../ProductVariants/OptionValueInterface.php | 24 +++ .../ProductVariantsProviderInterface.php | 25 +++ .../ProductVariantDataExporter/README.md | 3 + .../AbstractProductVariantsTest.php | 126 ++++++++++++ .../Test/Integration/VariantsRemovalTest.php | 75 +++++++ .../ProductVariantDataExporter/composer.json | 28 +++ .../etc/db_schema.xml | 52 +++++ .../etc/db_schema_whitelist.json | 17 ++ .../ProductVariantDataExporter/etc/di.xml | 61 ++++++ .../etc/et_schema.xml | 23 +++ .../etc/indexer.xml | 20 ++ .../ProductVariantDataExporter/etc/module.xml | 14 ++ .../ProductVariantDataExporter/etc/mview.xml | 14 ++ .../registration.php | 9 + 56 files changed, 2169 insertions(+), 77 deletions(-) create mode 100644 app/code/Magento/CatalogExport/Model/ProductVariantRepository.php create mode 100644 app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php create mode 100644 app/code/Magento/CatalogExportApi/Api/ProductVariantRepositoryInterface.php rename app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/{ConfigurableAttributeUid.php => ConfigurableOptionValueUid.php} (82%) create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableOptionValue.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Model/Query/ProductVariantsQuery.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php create mode 100644 app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php create mode 100644 app/code/Magento/ProductVariantDataExporter/LICENSE.txt create mode 100644 app/code/Magento/ProductVariantDataExporter/LICENSE_AFL.txt create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexMetadata.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeed.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeedInterface.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdFactory.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/OptionValueFactory.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/OptionValueInterface.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariantsProviderInterface.php create mode 100644 app/code/Magento/ProductVariantDataExporter/README.md create mode 100644 app/code/Magento/ProductVariantDataExporter/Test/Integration/AbstractProductVariantsTest.php create mode 100644 app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php create mode 100644 app/code/Magento/ProductVariantDataExporter/composer.json create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/db_schema.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/db_schema_whitelist.json create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/di.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/indexer.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/module.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/etc/mview.xml create mode 100644 app/code/Magento/ProductVariantDataExporter/registration.php diff --git a/app/code/Magento/CatalogDataExporter/etc/et_schema.xml b/app/code/Magento/CatalogDataExporter/etc/et_schema.xml index cb522f695..c17dfefdb 100644 --- a/app/code/Magento/CatalogDataExporter/etc/et_schema.xml +++ b/app/code/Magento/CatalogDataExporter/etc/et_schema.xml @@ -19,10 +19,6 @@ provider="Magento\CatalogDataExporter\Model\Provider\ProductMetadata"> - - - @@ -251,14 +247,6 @@ - - - - - - - - diff --git a/app/code/Magento/CatalogExport/Model/ChangedEntitiesMessageBuilder.php b/app/code/Magento/CatalogExport/Model/ChangedEntitiesMessageBuilder.php index 2ff0b8d03..b95ceb9f0 100644 --- a/app/code/Magento/CatalogExport/Model/ChangedEntitiesMessageBuilder.php +++ b/app/code/Magento/CatalogExport/Model/ChangedEntitiesMessageBuilder.php @@ -65,7 +65,7 @@ public function __construct( * * @return \Magento\CatalogExport\Event\Data\ChangedEntities */ - public function build(string $eventType, array $entities, string $scope): ChangedEntities + public function build(string $eventType, array $entities, ?string $scope = null): ChangedEntities { $meta = $this->metaFactory->create(); $meta->setScope($scope); diff --git a/app/code/Magento/CatalogExport/Model/Indexer/EntityIndexerCallback.php b/app/code/Magento/CatalogExport/Model/Indexer/EntityIndexerCallback.php index 5f4c6b19b..8c20fc336 100644 --- a/app/code/Magento/CatalogExport/Model/Indexer/EntityIndexerCallback.php +++ b/app/code/Magento/CatalogExport/Model/Indexer/EntityIndexerCallback.php @@ -103,21 +103,23 @@ public function __construct( public function execute(array $entityData, array $deleteIds) : void { foreach ($this->getDeleteEntitiesData($deleteIds) as $storeCode => $entities) { + $scope = $storeCode ?: null; foreach (\array_chunk($entities, $this->batchSize) as $chunk) { $this->publishMessage( $this->deletedEventType, $chunk, - $storeCode + $scope ); } } foreach ($this->getUpdateEntitiesData($entityData) as $storeCode => $entities) { + $scope = $storeCode ?: null; foreach (\array_chunk($entities, $this->batchSize) as $chunk) { $this->publishMessage( $this->updatedEventType, $chunk, - $storeCode + $scope ); } } @@ -135,8 +137,8 @@ private function getDeleteEntitiesData(array $deleteIds): array $deleted = []; $feed = $this->feedPool->getFeed($this->feedIndexMetadata->getFeedName()); foreach ($feed->getDeletedByIds($deleteIds) as $entity) { - $deleted[$entity['storeViewCode']][] = [ - 'entity_id' => (int)$entity[$this->feedIndexMetadata->getFeedIdentity()], + $deleted[$entity['storeViewCode'] ?? null][] = [ + 'entity_id' => (string)$entity[$this->feedIndexMetadata->getFeedIdentity()], ]; } @@ -154,8 +156,8 @@ private function getUpdateEntitiesData(array $entityData): array { $entitiesArray = []; foreach ($entityData as $entity) { - $entitiesArray[$entity['storeViewCode']][] = [ - 'entity_id' => (int)$entity[$this->feedIndexMetadata->getFeedIdentity()], + $entitiesArray[$entity['storeViewCode'] ?? null][] = [ + 'entity_id' => (string)$entity[$this->feedIndexMetadata->getFeedIdentity()], 'attributes' => $entity['attributes'] ?? [], ]; } @@ -168,11 +170,11 @@ private function getUpdateEntitiesData(array $entityData): array * * @param string $eventType * @param array $entities - * @param string $scope + * @param string|null $scope * * @return void */ - private function publishMessage(string $eventType, array $entities, string $scope): void + private function publishMessage(string $eventType, array $entities, ?string $scope): void { $message = $this->messageBuilder->build($eventType, $entities, $scope); diff --git a/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php new file mode 100644 index 000000000..99660dca7 --- /dev/null +++ b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php @@ -0,0 +1,182 @@ +feedPool = $feedPool; + $this->dtoMapper = $dtoMapper; + $this->productVariantFactory = $productVariantFactory; + $this->deploymentConfig = $deploymentConfig; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function get(array $ids): array + { + if (count($ids) > $this->getMaxItemsInResponse()) { + throw new \InvalidArgumentException( + 'Max items in the response can\'t exceed ' + . $this->getMaxItemsInResponse() + . '.' + ); + } + + $productsVariants = []; + $feedData = $this->feedPool->getFeed('variants')->getFeedByIds($ids); + if (empty($feedData['feed'])) { + $this->logger->error( + \sprintf('Cannot find products variants data in catalog feed with ids "%s"', \implode(',', $ids)) + ); + return $productsVariants; + } + + foreach ($feedData['feed'] as $feedItem) { + $productVariant = $this->productVariantFactory->create(); + $feedItem = $this->cleanUpNullValues($feedItem); + $this->dtoMapper->populateWithArray( + $productVariant, + $feedItem, + ProductVariant::class + ); + $productsVariants[] = $productVariant; + } + return $productsVariants; + } + + /** + * @inheritdoc + */ + public function getByProductIds(array $productIds): array + { + if (count($productIds) > $this->getMaxItemsInResponse()) { + throw new \InvalidArgumentException( + 'Max items in the response can\'t exceed ' + . $this->getMaxItemsInResponse() + . '.' + ); + } + + $productsVariants = []; + $feedData = $this->feedPool->getFeed('variants')->getFeedByProductIds($productIds); + if (empty($feedData['feed'])) { + $this->logger->error( + \sprintf( + 'Cannot find products variants data in catalog feed with product ids "%s"', + \implode(',', $productIds) + ) + ); + return $productsVariants; + } + + foreach ($feedData['feed'] as $feedItem) { + $productVariant = $this->productVariantFactory->create(); + $feedItem = $this->cleanUpNullValues($feedItem); + $this->dtoMapper->populateWithArray( + $productVariant, + $feedItem, + ProductVariant::class + ); + $productsVariants[] = $productVariant; + } + return $productsVariants; + } + + /** + * Get max items in response + * + * @return int + */ + private function getMaxItemsInResponse(): int + { + try { + $maxItemsInResponse = (int)$this->deploymentConfig->get('catalog_export/max_items_in_response'); + } catch (\Exception $e) { + $this->logger->error( + \sprintf('Cannot retrieve catalog export max items in response for product variants. ' . $e) + ); + return self::MAX_ITEMS_IN_RESPONSE; + } + return $maxItemsInResponse ?: self::MAX_ITEMS_IN_RESPONSE; + } + + /** + * Unset null values in provided array recursively + * + * @param array $array + * @return array + */ + private function cleanUpNullValues(array $array): array + { + $result = []; + foreach ($array as $key => $value) { + if ($value === null || $value === '') { + continue; + } + + $result[$key] = is_array($value) ? $this->cleanUpNullValues($value) : $value; + } + return $result; + } +} diff --git a/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php b/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php new file mode 100644 index 000000000..63c03255f --- /dev/null +++ b/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php @@ -0,0 +1,141 @@ +productVariantsFeed = $objectManager->get(FeedPool::class)->getFeed('variants'); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + + $this->createServiceInfo = [ + 'get' => [ + 'rest' => [ + 'resourcePath' => '/V1/catalog-export/product-variants', + 'httpMethod' => Request::HTTP_METHOD_GET, + ] + ], + 'getByProductIds' => [ + 'rest' => [ + 'resourcePath' => '/V1/catalog-export/product-variants/product-ids', + 'httpMethod' => Request::HTTP_METHOD_GET, + ] + ] + ]; + } + + /** + * Test configurable product variant get endpoint + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes.php + * @return void + */ + public function testGetVariantById(): void + { + $this->_markTestAsRestOnly('SOAP will be covered in another test'); + try { + $configurable = $this->productRepository->get('configurable'); + $configurableId = $configurable->getId(); + $simple = $this->productRepository->get('simple_10'); + $simpleId = $simple->getId(); + $variantId = \base64_encode(\sprintf( + 'configurable/%1$s/%2$s', + $configurableId, + $simpleId, + )); + $this->createServiceInfo['get']['rest']['resourcePath'] .= '?ids[0]=' . $variantId; + $apiResult = $this->_webApiCall($this->createServiceInfo['get'], []); + $variantFeed = $this->productVariantsFeed->getFeedByIds([$variantId])['feed']; + + foreach (array_keys($variantFeed) as $feedKey) { + unset($variantFeed[$feedKey]['modifiedAt'], $variantFeed[$feedKey]['deleted']); + } + + $this->assertVariantsEqual($variantFeed, $apiResult); + } catch (NoSuchEntityException $e) { + $this->fail('Expected product could not be retrieved'); + } + } + + /** + * Test configurable product variant getByProductId endpoint + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes.php + * @return void + */ + public function testGetVariantByProductId(): void + { + $this->_markTestAsRestOnly('SOAP will be covered in another test'); + + try { + $product = $this->productRepository->get('configurable'); + $productId = $product->getId(); + } catch (NoSuchEntityException $e) { + $this->fail('Expected product could not be retrieved'); + } + + $this->createServiceInfo['getByProductIds']['rest']['resourcePath'] .= '?productIds[0]=' . $productId; + $apiResult = $this->_webApiCall($this->createServiceInfo['getByProductIds'], []); + $variantFeed = $this->productVariantsFeed->getFeedByProductIds([$productId])['feed']; + foreach (array_keys($variantFeed) as $feedKey) { + unset($variantFeed[$feedKey]['modifiedAt'], $variantFeed[$feedKey]['deleted']); + } + + $this->assertVariantsEqual($variantFeed, $apiResult); + } + + /** + * Assert that the arrays returned by feed and exportAPI contain the same variants. + * + * @param array $variantFeed + * @param array $apiResult + * @return void + */ + private function assertVariantsEqual(array $variantFeed, array $apiResult): void + { + $diff = $this->compareArraysRecursively->execute($variantFeed, $apiResult); + $this->assertEquals([], $diff, "Actual categories response doesn't equal expected data"); + } +} diff --git a/app/code/Magento/CatalogExport/composer.json b/app/code/Magento/CatalogExport/composer.json index 5ec84f5f8..790c6930a 100644 --- a/app/code/Magento/CatalogExport/composer.json +++ b/app/code/Magento/CatalogExport/composer.json @@ -9,6 +9,7 @@ "magento/framework": "*", "magento/framework-message-queue": "*", "magento/module-catalog-export-api": "*", + "magento/module-product-variant-data-exporter": "*", "magento/module-data-exporter": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/CatalogExport/etc/communication.xml b/app/code/Magento/CatalogExport/etc/communication.xml index ad696c117..cb72c7ed4 100644 --- a/app/code/Magento/CatalogExport/etc/communication.xml +++ b/app/code/Magento/CatalogExport/etc/communication.xml @@ -8,4 +8,5 @@ + diff --git a/app/code/Magento/CatalogExport/etc/di.xml b/app/code/Magento/CatalogExport/etc/di.xml index c755c1974..f47d80e14 100644 --- a/app/code/Magento/CatalogExport/etc/di.xml +++ b/app/code/Magento/CatalogExport/etc/di.xml @@ -8,8 +8,10 @@ + + @@ -31,6 +33,16 @@ + + + Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantFeedIndexMetadata + catalog.export.product.variants.data + product_variants_updated + product_variants_deleted + + + diff --git a/app/code/Magento/CatalogExport/etc/module.xml b/app/code/Magento/CatalogExport/etc/module.xml index ccccaceff..2d50f0efc 100644 --- a/app/code/Magento/CatalogExport/etc/module.xml +++ b/app/code/Magento/CatalogExport/etc/module.xml @@ -9,6 +9,7 @@ + diff --git a/app/code/Magento/CatalogExport/etc/queue_publisher.xml b/app/code/Magento/CatalogExport/etc/queue_publisher.xml index d143286af..b6b75d6f1 100644 --- a/app/code/Magento/CatalogExport/etc/queue_publisher.xml +++ b/app/code/Magento/CatalogExport/etc/queue_publisher.xml @@ -12,4 +12,7 @@ + + + diff --git a/app/code/Magento/CatalogExport/etc/queue_topology.xml b/app/code/Magento/CatalogExport/etc/queue_topology.xml index 359b2498f..176b35ecd 100644 --- a/app/code/Magento/CatalogExport/etc/queue_topology.xml +++ b/app/code/Magento/CatalogExport/etc/queue_topology.xml @@ -9,5 +9,6 @@ + diff --git a/app/code/Magento/CatalogExport/etc/webapi.xml b/app/code/Magento/CatalogExport/etc/webapi.xml index 5f95a8853..bb45f8f32 100644 --- a/app/code/Magento/CatalogExport/etc/webapi.xml +++ b/app/code/Magento/CatalogExport/etc/webapi.xml @@ -23,4 +23,20 @@ + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/CatalogExportApi/Api/Data/ProductVariant.php b/app/code/Magento/CatalogExportApi/Api/Data/ProductVariant.php index 86a5aca21..a17e4af92 100644 --- a/app/code/Magento/CatalogExportApi/Api/Data/ProductVariant.php +++ b/app/code/Magento/CatalogExportApi/Api/Data/ProductVariant.php @@ -30,7 +30,7 @@ class ProductVariant private $optionValues; /** @var string */ - private $productId; + private $parentId; /** * Get id @@ -75,23 +75,23 @@ public function setOptionValues(?array $optionValues = null): void } /** - * Get product id + * Get parent id * * @return string */ - public function getProductId(): ?string + public function getParentId(): ?string { - return $this->productId; + return $this->parentId; } /** - * Set product id + * Set parent id * - * @param string $productId + * @param string $parentId * @return void */ - public function setProductId(?string $productId): void + public function setParentId(?string $parentId): void { - $this->productId = $productId; + $this->parentId = $parentId; } } diff --git a/app/code/Magento/CatalogExportApi/Api/ProductVariantRepositoryInterface.php b/app/code/Magento/CatalogExportApi/Api/ProductVariantRepositoryInterface.php new file mode 100644 index 000000000..229b1db50 --- /dev/null +++ b/app/code/Magento/CatalogExportApi/Api/ProductVariantRepositoryInterface.php @@ -0,0 +1,30 @@ +productOptionQuery = $productOptionQuery; $this->productOptionValueQuery = $productOptionValueQuery; $this->queryGenerator = $queryGenerator; - $this->configurableAttributeUid = $configurableAttributeUid; + $this->optionValueUid = $optionValueUid; $this->logger = $logger; $this->batchSize = $batchSize; } @@ -125,7 +125,7 @@ private function getOptionValuesData(array $arguments): array foreach ($this->getBatchedQueryData($select, 'attribute_id') as $batchData) { foreach ($batchData as $row) { $optionValues[$row['attribute_id']][$row['storeViewCode']][$row['optionId']] = [ - 'id' => $this->configurableAttributeUid->resolve($row['attribute_id'], $row['optionId']), + 'id' => $this->optionValueUid->resolve($row['attribute_id'], $row['optionId']), 'label' => $row['label'], //TODO: should be deleted in catalog-storefront/issues/304 @@ -150,7 +150,7 @@ private function formatOptionsRow($row): array 'storeViewCode' => $row['storeViewCode'], 'productOptions' => [ 'id' => $row['attribute_code'], - 'type' => ConfigurableAttributeUid::OPTION_TYPE, + 'type' => ConfigurableOptionValueUid::OPTION_TYPE, 'label' => $row['label'], 'sort_order' => $row['position'] ], @@ -159,7 +159,7 @@ private function formatOptionsRow($row): array 'options' => [ 'id' => $row['super_attribute_id'], 'sort_order' => $row['position'], - 'type' => ConfigurableAttributeUid::OPTION_TYPE, + 'type' => ConfigurableOptionValueUid::OPTION_TYPE, 'attribute_id' => $row['attribute_id'], 'attribute_code' => $row['attribute_code'], 'use_default' => (bool)$row['use_default'], diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php new file mode 100644 index 000000000..526ed9911 --- /dev/null +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php @@ -0,0 +1,134 @@ +resourceConnection = $resourceConnection; + $this->variantsOptionValuesQuery = $variantsOptionValuesQuery; + $this->logger = $logger; + $this->optionValueUid = $optionValueUid; + $this->optionValueFactory = $optionValueFactory; + $this->idFactory = $idFactory; + } + + /** + * @inheritDoc + * + * @throws UnableRetrieveData + */ + public function get(array $values): array + { + $output = []; + $parentIds = []; + foreach ($values as $value) { + $parentIds[$value['parent_id']] = $value['parent_id']; + } + + try { + $variants = $this->getVariants($parentIds); + foreach ($variants as $id => $optionValues) { + $output[] = [ + 'id' => $id, + 'option_values' => $optionValues['optionValues'], + 'parent_id' => $optionValues['parentId'], + ]; + } + } catch (\Throwable $exception) { + $this->logger->error($exception->getMessage()); + throw new UnableRetrieveData('Unable to retrieve configurable product variants data'); + } + return $output; + } + + /** + * Get configurable product variants + * + * @param array $parentIds + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function getVariants(array $parentIds): array + { + $variants = []; + $idResolver = $this->idFactory->get('configurable'); + $optionValueResolver = $this->optionValueFactory->get('configurable'); + + $cursor = $this->resourceConnection->getConnection()->query( + $this->variantsOptionValuesQuery->getQuery($parentIds) + ); + while ($row = $cursor->fetch()) { + $id = $idResolver->resolve($row['parentId'], $row['childId']); + $optionValueUid = ($this->optionValueUid->resolve( + $row['attributeId'], + $row['attributeValue'] + )); + $optionValue = $optionValueResolver->resolve($row['parentId'], $row['attributeCode'], $optionValueUid); + $variants[$id]['parentId'] = $row['parentId']; + $variants[$id]['optionValues'][] = $optionValue; + } + return $variants; + } +} diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php new file mode 100644 index 000000000..b36ebdb0f --- /dev/null +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php @@ -0,0 +1,35 @@ +resourceConnection = $resourceConnection; + } + + /** + * Get query for provider + * + * @param array $parentIds + * @return Select + */ + public function getQuery(array $parentIds): Select + { + $connection = $this->resourceConnection->getConnection(); + $joinField = $connection->getAutoIncrementField( + $this->resourceConnection->getTableName('catalog_product_entity') + ); + + $subSelect = $connection->select() + ->from( + ['cpsa' => $this->resourceConnection->getTableName('catalog_product_super_attribute')], + ['attribute_id'] + ) + ->where(\sprintf('cpsa.product_id IN (cpe.%s)', $joinField)); + $select = $connection->select() + ->from( + ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')], + [] + ) + ->joinInner( + ['cpsl' => $this->resourceConnection->getTableName('catalog_product_super_link')], + \sprintf('cpsl.parent_id = cpe.%s', $joinField), + [] + ) + ->joinInner( + ['cpec' => $this->resourceConnection->getTableName('catalog_product_entity')], + 'cpec.entity_id = cpsl.product_id', + [] + ) + ->joinInner( + ['cpei' => $this->resourceConnection->getTableName(['catalog_product_entity', 'int'])], + \sprintf( + 'cpei.%1$s = cpec.%1$s AND cpei.attribute_id IN (%2$s)', + $joinField, + $subSelect->assemble() + ), + [] + ) + ->joinInner( + ['ea' => $this->resourceConnection->getTableName('eav_attribute')], + 'ea.attribute_id = cpei.attribute_id', + [] + ) + ->columns( + [ + 'parentId' => 'cpe.entity_id', + 'childId' => 'cpec.entity_id', + 'attributeId' => 'cpei.attribute_id', + 'attributeCode' => 'ea.attribute_code', + 'attributeValue' => 'cpei.value' + ] + ) + ->where('cpe.entity_id IN (?)', $parentIds); + return $select; + } +} diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php b/app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php new file mode 100644 index 000000000..f98f3b7bc --- /dev/null +++ b/app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php @@ -0,0 +1,73 @@ +resourceConnection = $resourceConnection; + } + + /** + * Get query + * + * @param int $productId + * @return Select + */ + public function getQuery(int $productId): Select + { + $connection = $this->resourceConnection->getConnection(); + $joinField = $connection->getAutoIncrementField( + $this->resourceConnection->getTableName('catalog_product_entity') + ); + $select = $connection->select() + ->from( + ['cpsl' => $this->resourceConnection->getTableName('catalog_product_super_link')], + [] + ) + ->joinInner( + ['cpsa' => $this->resourceConnection->getTableName('catalog_product_super_attribute')], + 'cpsa.product_id = cpsl.parent_id', + [] + ) + ->joinInner( + ['ea' => $this->resourceConnection->getTableName('eav_attribute')], + 'ea.attribute_id = cpsa.attribute_id', + [] + ) + ->joinInner( + ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')], + sprintf('cpe.%1$s = cpsa.product_id', $joinField), + [] + ) + ->columns( + [ + 'parentId' => 'cpe.entity_id', + 'attributeCode' => 'ea.attribute_code', + ] + ) + ->where('cpsl.product_id = ?', $productId); + return $select; + } +} diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php new file mode 100644 index 000000000..edad7f9bf --- /dev/null +++ b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php @@ -0,0 +1,84 @@ +resourceConnection = $resourceConnection; + $this->linkedAttributesQuery = $linkedAttributesQuery; + $this->feedIndexer = $feedIndexer; + } + + /** + * Reindex parent product on change of super attribute value. + * + * @param ResourceProduct $subject + * @param ResourceProduct $result + * @param AbstractModel $product + * @return ResourceProduct + * @throws \Zend_Db_Statement_Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSave( + ResourceProduct $subject, + ResourceProduct $result, + AbstractModel $product + ): ResourceProduct { + $type = $product->getTypeId(); + if ($type === Type::TYPE_SIMPLE || $type === Type::TYPE_VIRTUAL) { + $select = $this->linkedAttributesQuery->getQuery((int)$product->getId()); + $connection = $this->resourceConnection->getConnection(); + $configurableLinks = $connection->query($select)->fetchAll(); + $changedConfigurableIds = []; + foreach ($configurableLinks as $link) { + if ($product->getOrigData($link['attributeCode']) !== $product->getData($link['attributeCode'])) { + $changedConfigurableIds[] = $link['parentId']; + } + } + if (!empty($changedConfigurableIds)) { + $this->feedIndexer->executeList(array_filter($changedConfigurableIds)); + } + } + return $result; + } +} diff --git a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php new file mode 100644 index 000000000..16dd6c475 --- /dev/null +++ b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php @@ -0,0 +1,96 @@ +productRepository->get('configurable'); + $simples[] = $this->productRepository->get('simple_10'); + $simples[] = $this->productRepository->get('simple_20'); + $expected = $this->getExpectedProductVariants($configurable, $simples); + + $feed = $this->productVariantsFeed; + $actual = $feed->getFeedByProductIds( + [$configurable->getId()] + )['feed']; + + $diff = $this->arrayUtils->recursiveDiff($expected, $actual); + self::assertEquals([], $diff, "Product variants response doesn't equal expected response"); + + } catch (NoSuchEntityException $e) { + $this->fail('Test products could not be retrieved'); + } + } + + /** + * Get the expected variants for the first combination of options being tested. + * + * @param Product $configurable + * @param Product[] $simples + * @return array + */ + private function getExpectedProductVariants(Product $configurable, array $simples): array + { + $configurableOptions = $configurable->getExtensionAttributes()->getConfigurableProductOptions(); + $variants = []; + + foreach ($simples as $simple) { + $id = (\sprintf( + 'configurable/%1$s/%2$s', + $configurable->getId(), + $simple->getId(), + )); + $optionValues = []; + foreach ($configurableOptions as $configurableOption) { + $attributeCode = $configurableOption->getProductAttribute()->getAttributeCode(); + foreach ($configurableOption->getValues() as $configurableOptionValue) { + if ($simple->getData($attributeCode) === $configurableOptionValue->getValueIndex()) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $optionUid = \base64_encode(\sprintf( + 'configurable/%1$s/%2$s', + $configurableOption->getAttributeId(), + $configurableOptionValue->getValueIndex() + )); + $optionValues[] = \sprintf( + '%1$s:%2$s/%3$s', + $configurable->getId(), + $attributeCode, + $optionUid + ); + } + } + } + $variants[$id] = [ + 'id' => $id, + 'option_values' => $optionValues, + 'parent_id' => $configurable->getId(), + 'deleted' => false + ]; + } + + return array_values($variants); + } +} diff --git a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php index 6ffd9ba69..8eba8b9e7 100755 --- a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php @@ -10,7 +10,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\CatalogDataExporter\Test\Integration\AbstractProductTestHelper; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\ConfigurableProductDataExporter\Model\Provider\Product\ConfigurableAttributeUid; +use Magento\ConfigurableProductDataExporter\Model\Provider\Product\ConfigurableOptionValueUid; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\TestFramework\Helper\Bootstrap; @@ -27,9 +27,9 @@ class ConfigurableProductsTest extends AbstractProductTestHelper private $configurable; /** - * @var ConfigurableAttributeUid + * @var ConfigurableOptionValueUid */ - private $configurableAttributeUid; + private $optionValueUid; /** * Setup tests @@ -38,7 +38,7 @@ protected function setUp(): void { parent::setUp(); $this->configurable = Bootstrap::getObjectManager()->create(Configurable::class); - $this->configurableAttributeUid = Bootstrap::getObjectManager()->create(ConfigurableAttributeUid::class); + $this->optionValueUid = Bootstrap::getObjectManager()->create(ConfigurableOptionValueUid::class); } /** @@ -202,7 +202,6 @@ private function validateVariantsData(ProductInterface $product, array $extract, /** * Get partially hardcoded option values to compare to extracted product data * - * @param string $attributeId * @param array $optionValues * @return array */ @@ -211,7 +210,7 @@ private function getOptionValues(string $attributeId, array $optionValues) : arr $values = []; $i = 1; foreach ($optionValues as $optionValue) { - $id = $this->configurableAttributeUid->resolve($attributeId, $optionValue['value_index']); + $id = $this->optionValueUid->resolve($attributeId, $optionValue['value_index']); $values[$id] = [ 'id' => $id, 'label' => 'Option ' . $i, diff --git a/app/code/Magento/ConfigurableProductDataExporter/composer.json b/app/code/Magento/ConfigurableProductDataExporter/composer.json index 1f64c0a03..ec921ec47 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/composer.json +++ b/app/code/Magento/ConfigurableProductDataExporter/composer.json @@ -22,6 +22,7 @@ "magento/framework": "*", "magento/module-configurable-product": "*", "magento/module-data-exporter": "*", + "magento/module-product-variant-data-exporter": "*", "magento/module-catalog-data-exporter": "*", "magento/module-catalog": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/ConfigurableProductDataExporter/etc/di.xml b/app/code/Magento/ConfigurableProductDataExporter/etc/di.xml index 45db7cbc2..4a40b3b93 100755 --- a/app/code/Magento/ConfigurableProductDataExporter/etc/di.xml +++ b/app/code/Magento/ConfigurableProductDataExporter/etc/di.xml @@ -13,6 +13,27 @@ + + + + \Magento\ConfigurableProductDataExporter\Model\Provider\Product\ProductVariants + + + + + + + Magento\ConfigurableProductDataExporter\Model\Provider\Product\ProductVariants\ConfigurableOptionValue + + + + + + + Magento\ConfigurableProductDataExporter\Model\Provider\Product\ProductVariants\ConfigurableId + + + @@ -26,4 +47,8 @@ 1000 + + + diff --git a/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml b/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml index 6d2e4bafb..7e117c0f2 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml +++ b/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml @@ -14,6 +14,7 @@ + diff --git a/app/code/Magento/DataExporter/Model/Feed.php b/app/code/Magento/DataExporter/Model/Feed.php index 7037e1c2a..929092dd5 100644 --- a/app/code/Magento/DataExporter/Model/Feed.php +++ b/app/code/Magento/DataExporter/Model/Feed.php @@ -21,22 +21,22 @@ class Feed implements FeedInterface * * @var int */ - private const OFFSET = 100; + protected const OFFSET = 100; /** * @var ResourceConnection */ - private $resourceConnection; + protected $resourceConnection; /** * @var SerializerInterface */ - private $serializer; + protected $serializer; /** * @var FeedIndexMetadata */ - private $feedIndexMetadata; + protected $feedIndexMetadata; /** * @param ResourceConnection $resourceConnection @@ -159,7 +159,7 @@ public function getDeletedByIds(array $ids, ?array $storeViewCodes = []): array * @return array * @throws \Zend_Db_Statement_Exception */ - private function fetchData($select, array $attributes): array + protected function fetchData($select, array $attributes): array { $connection = $this->resourceConnection->getConnection(); $recentTimestamp = null; diff --git a/app/code/Magento/DataExporter/Model/FeedInterface.php b/app/code/Magento/DataExporter/Model/FeedInterface.php index db46c66d1..44c606274 100644 --- a/app/code/Magento/DataExporter/Model/FeedInterface.php +++ b/app/code/Magento/DataExporter/Model/FeedInterface.php @@ -27,7 +27,7 @@ public function getFeedSince(string $timestamp, ?array $storeViewCodes = [], arr /** * Get feed data by IDs * - * @param int[] $ids + * @param string[]|int[] $ids * @param null|string[] $storeViewCodes * @param array $attributes // entity_id => attributes_array relation * @@ -38,7 +38,7 @@ public function getFeedByIds(array $ids, ?array $storeViewCodes = [], array $att /** * Get deleted entities by IDs * - * @param string[] $ids + * @param string[]|int[] $ids * @param null|string[] $storeViewCodes * @return array */ diff --git a/app/code/Magento/DataExporter/Model/Indexer/DataSerializer.php b/app/code/Magento/DataExporter/Model/Indexer/DataSerializer.php index e0412d810..2fe5063ba 100644 --- a/app/code/Magento/DataExporter/Model/Indexer/DataSerializer.php +++ b/app/code/Magento/DataExporter/Model/Indexer/DataSerializer.php @@ -49,7 +49,13 @@ public function serialize(array $data): array $outputRow = []; $outputRow['feed_data'] = $this->serializer->serialize($row); foreach ($this->mapping as $field => $index) { - $outputRow[$field] = isset($row[$index]) ? $row[$index] : null; + if (isset($row[$index])) { + $outputRow[$field] = is_array($row[$index]) ? + $this->serializer->serialize($row[$index]) : + $row[$index]; + } else { + $outputRow[$field] = null; + } } $output[] = $outputRow; } diff --git a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexMetadata.php b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexMetadata.php index dadcd9a7d..d4dbb9793 100644 --- a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexMetadata.php +++ b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexMetadata.php @@ -15,42 +15,42 @@ class FeedIndexMetadata /** * @var string */ - private $feedName; + protected $feedName; /** * @var string */ - private $sourceTableName; + protected $sourceTableName; /** * @var string */ - private $sourceTableField; + protected $sourceTableField; /** * @var string */ - private $feedIdentity; + protected $feedIdentity; /** * @var string */ - private $feedTableName; + protected $feedTableName; /** * @var string */ - private $feedTableField; + protected $feedTableField; /** - * @var int + * @var string[] */ - private $batchSize; + protected $feedTableMutableColumns; /** - * @var string[] + * @var int */ - private $feedTableMutableColumns; + protected $batchSize; /** * @param string $feedName @@ -72,14 +72,14 @@ public function __construct( array $feedTableMutableColumns, int $batchSize = 100 ) { + $this->feedName = $feedName; $this->sourceTableName = $sourceTableName; $this->sourceTableField = $sourceTableField; $this->feedIdentity = $feedIdentity; $this->feedTableName = $feedTableName; $this->feedTableField = $feedTableField; - $this->batchSize = $batchSize; - $this->feedName = $feedName; $this->feedTableMutableColumns = $feedTableMutableColumns; + $this->batchSize = $batchSize; } /** diff --git a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php index eaab78cc5..3d4c3e4f2 100644 --- a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php +++ b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php @@ -22,37 +22,37 @@ class FeedIndexer implements IndexerActionInterface, MviewActionInterface /** * @var Processor */ - private $processor; + protected $processor; /** * @var ResourceConnection */ - private $resourceConnection; + protected $resourceConnection; /** * @var FeedIndexMetadata */ - private $feedIndexMetadata; + protected $feedIndexMetadata; /** * @var DataSerializerInterface */ - private $dataSerializer; + protected $dataSerializer; /** * @var FeedIndexerCallbackInterface */ - private $feedIndexerCallback; + protected $feedIndexerCallback; /** * @var array */ - private $callbackSkipAttributes; + protected $callbackSkipAttributes; /** * @var FeedPool */ - private $feedPool; + protected $feedPool; /** * @param Processor $processor @@ -87,7 +87,7 @@ public function __construct( * @param int $lastKnownId * @return Select */ - private function getIdsSelect(int $lastKnownId) : Select + protected function getIdsSelect(int $lastKnownId) : Select { $columnExpression = sprintf('s.%s', $this->feedIndexMetadata->getSourceTableField()); $whereClause = sprintf('s.%s > ?', $this->feedIndexMetadata->getSourceTableField()); @@ -219,7 +219,7 @@ private function prepareIndexData(array $indexData): array * * @return void */ - private function process($indexData = []) : void + protected function process($indexData = []) : void { $feedIdentity = $this->feedIndexMetadata->getFeedIdentity(); $data = $this->processor->process($this->feedIndexMetadata->getFeedName(), $indexData); @@ -317,7 +317,7 @@ private function fetchFeedData(array $ids): array * @param array $ids * @return void */ - private function markRemoved(array $ids) : void + protected function markRemoved(array $ids) : void { $connection = $this->resourceConnection->getConnection(); $select = $connection->select() @@ -347,7 +347,7 @@ private function markRemoved(array $ids) : void * * @return void */ - private function truncateFeedTable(): void + protected function truncateFeedTable(): void { $connection = $this->resourceConnection->getConnection(); $feedTable = $this->resourceConnection->getTableName($this->feedIndexMetadata->getFeedTableName()); diff --git a/app/code/Magento/ProductVariantDataExporter/LICENSE.txt b/app/code/Magento/ProductVariantDataExporter/LICENSE.txt new file mode 100644 index 000000000..36b2459f6 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/ProductVariantDataExporter/LICENSE_AFL.txt b/app/code/Magento/ProductVariantDataExporter/LICENSE_AFL.txt new file mode 100644 index 000000000..496bf10ad --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2016 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexMetadata.php b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexMetadata.php new file mode 100644 index 000000000..4eb7113fc --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexMetadata.php @@ -0,0 +1,66 @@ +feedTableParentField = $feedTableParentField; + } + + /** + * Get feed table product field + * + * @return string + */ + public function getFeedTableParentField(): string + { + return $this->feedTableParentField; + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php new file mode 100644 index 000000000..c7cbcb14e --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php @@ -0,0 +1,189 @@ +feedIndexMetadata->getSourceTableField(); + $columnExpression = sprintf( + 's.%s', + $sourceTableField + ); + $whereClause = sprintf('s.%s > ?', $sourceTableField); + $connection = $this->resourceConnection->getConnection(); + return $connection->select() + ->from( + ['s' => $this->feedIndexMetadata->getSourceTableName()], + [ + $this->feedIndexMetadata->getFeedTableParentField() => 's.' . $sourceTableField + ] + ) + ->where($whereClause, $lastKnownId) + ->order($columnExpression) + ->limit($this->feedIndexMetadata->getBatchSize()); + } + + /** + * Get all product IDs + * + * @return \Generator + */ + protected function getAllIds(): ?\Generator + { + $connection = $this->resourceConnection->getConnection(); + $lastKnownId = 0; + $continueReindex = true; + while ($continueReindex) { + $ids = $connection->fetchAll($this->getIdsSelect((int)$lastKnownId)); + if (empty($ids)) { + $continueReindex = false; + } else { + yield $ids; + $lastKnownId = end($ids)[$this->feedIndexMetadata->getFeedTableParentField()]; + } + } + } + + /** + * Execute full indexation + * + * @return void + * @throws \Zend_Db_Statement_Exception + */ + public function executeFull(): void + { + $this->truncateFeedTable(); + foreach ($this->getAllIds() as $ids) { + $this->process($ids); + } + } + + /** + * Execute partial indexation by ID list + * + * @param int[] $ids + * @return void + */ + public function executeList(array $ids): void + { + $arguments = []; + foreach ($ids as $id) { + $arguments[] = [$this->feedIndexMetadata->getFeedTableParentField() => $id]; + } + $this->process($arguments); + } + + /** + * Execute partial indexation by ID + * + * @param int $id + * @return void + */ + public function executeRow($id): void + { + $this->process([[$this->feedIndexMetadata->getFeedTableParentField() => $id]]); + } + + /** + * Execute materialization on ids entities + * + * @param int[] $ids + * @return void + * @api + */ + public function execute($ids): void + { + $arguments = []; + foreach ($ids as $id) { + $arguments[] = [$this->feedIndexMetadata->getFeedTableParentField() => $id]; + } + + $this->process($arguments); + } + + /** + * Indexer feed data processor + * + * TODO: currently reindexAll is going reindex all the products, should base that on catalog_product_relation + * + * @param array $indexData + * @return void + */ + protected function process($indexData = []): void + { + $feedIdentity = $this->feedIndexMetadata->getFeedIdentity(); + $data = $this->processor->process($this->feedIndexMetadata->getFeedName(), $indexData); + $deleteIds = $this->fetchFeedDataIds( + \array_column($indexData, $this->feedIndexMetadata->getFeedTableParentField()) + ); + $chunks = array_chunk($data, $this->feedIndexMetadata->getBatchSize()); + $connection = $this->resourceConnection->getConnection(); + foreach ($chunks as $chunk) { + foreach ($chunk as $updatedEntity) { + if (array_key_exists($updatedEntity[$feedIdentity], $deleteIds)) { + unset($deleteIds[$updatedEntity[$feedIdentity]]); + } + } + $connection->insertOnDuplicate( + $this->resourceConnection->getTableName($this->feedIndexMetadata->getFeedTableName()), + $this->dataSerializer->serialize($chunk), + $this->feedIndexMetadata->getFeedTableMutableColumns() + ); + } + $this->markRemoved($deleteIds); + $this->feedIndexerCallback->execute($data, $deleteIds); + } + + /** + * Fetch feed data + * + * @param array $ids + * @return array + */ + protected function fetchFeedDataIds(array $ids): array + { + $feedIdentity = $this->feedIndexMetadata->getFeedIdentity(); + $feedData = $this->feedPool->getFeed($this->feedIndexMetadata->getFeedName())->getFeedByProductIds($ids); + $output = []; + + foreach ($feedData['feed'] as $feedItem) { + $output[$feedItem[$feedIdentity]] = $feedItem[$feedIdentity]; + } + return $output; + } + + /** + * Mark entities as removed + * + * @param array $ids + * @return void + */ + protected function markRemoved(array $ids): void + { + $connection = $this->resourceConnection->getConnection(); + $connection->update( + $this->resourceConnection->getTableName($this->feedIndexMetadata->getFeedTableName()), + ['is_deleted' => new \Zend_Db_Expr('1')], + [\sprintf('%s IN (?)', $this->feedIndexMetadata->getFeedTableField()) => $ids] + ); + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeed.php b/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeed.php new file mode 100644 index 000000000..ac522402a --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeed.php @@ -0,0 +1,66 @@ +resourceConnection->getConnection(); + + $select = $connection->select() + ->from( + ['t' => $this->resourceConnection->getTableName($this->feedIndexMetadata->getFeedTableName())], + [ + 'feed_data', + 'modified_at', + 'is_deleted' + ] + ) + ->where('t.is_deleted = ?', 0) + ->where(sprintf('t.%s IN (?)', $this->feedIndexMetadata->getFeedTableParentField()), $entityIds); + + return $this->fetchData($select, []); + } + + /** + * @inheritDoc + */ + public function getDeletedByProductIds(array $entityIds): array + { + $connection = $this->resourceConnection->getConnection(); + + $select = $connection->select() + ->from( + ['t' => $this->resourceConnection->getTableName($this->feedIndexMetadata->getFeedTableName())], + [ + 'feed_data', + ] + ) + ->where('t.is_deleted = ?', 1) + ->where(sprintf('t.%s IN (?)', $this->feedIndexMetadata->getFeedTableParentField()), $entityIds); + + $connection = $this->resourceConnection->getConnection(); + $cursor = $connection->query($select); + + $output = []; + while ($row = $cursor->fetch()) { + $output[] = $this->serializer->unserialize($row['feed_data']); + } + + return $output; + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeedInterface.php b/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeedInterface.php new file mode 100644 index 000000000..856034c43 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/ProductVariantFeedInterface.php @@ -0,0 +1,32 @@ +variantsProviders = $variantsProviders; + } + + /** + * @inheritdoc + * @throws UnableRetrieveData + */ + public function get(array $values): array + { + $variants = []; + foreach ($this->variantsProviders as $provider) { + $variants = $variants + $provider->get($values); + } + return $variants; + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdFactory.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdFactory.php new file mode 100644 index 000000000..a53221595 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdFactory.php @@ -0,0 +1,63 @@ +objectManager = $objectManager; + $this->variantTypes = $variantTypes; + } + + /** + * Returns product variant id provider object + * + * @param string $typeName + * @return IdInterface + * @throws \InvalidArgumentException + */ + public function get(string $typeName): IdInterface + { + if (!isset($this->variantTypes[$typeName])) { + throw new \InvalidArgumentException( + \sprintf('Product variant id provider for type %s not registered', $typeName) + ); + } + if (!isset($this->registry[$typeName])) { + $this->registry[$typeName] = $this->objectManager->get($this->variantTypes[$typeName]); + } + return $this->registry[$typeName]; + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php new file mode 100644 index 000000000..74c975542 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php @@ -0,0 +1,23 @@ +objectManager = $objectManager; + $this->variantTypes = $variantTypes; + } + + /** + * Returns product variant option value provider object + * + * @param string $typeName + * @return OptionValueInterface + * @throws \InvalidArgumentException + */ + public function get(string $typeName): OptionValueInterface + { + if (!isset($this->variantTypes[$typeName])) { + throw new \InvalidArgumentException( + \sprintf('Product variant option value provider for type %s not registered', $typeName) + ); + } + if (!isset($this->registry[$typeName])) { + $this->registry[$typeName] = $this->objectManager->get($this->variantTypes[$typeName]); + } + return $this->registry[$typeName]; + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/OptionValueInterface.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/OptionValueInterface.php new file mode 100644 index 000000000..2bd28e360 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/OptionValueInterface.php @@ -0,0 +1,24 @@ +resource = Bootstrap::getObjectManager()->create(ResourceConnection::class); + $this->connection = $this->resource->getConnection(); + $this->indexer = Bootstrap::getObjectManager()->create(Indexer::class); + $this->jsonSerializer = Bootstrap::getObjectManager()->create(Json::class); + $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); + $this->storeManager = Bootstrap::getObjectManager()->create(StoreManagerInterface::class); + $this->productVariantsFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('variants'); + $this->optionValueUid = Bootstrap::getObjectManager()->create(OptionValueUid::class); + $this->attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepository::class); + $this->arrayUtils = $objectManager->create(ArrayUtils::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + } + + /** + * Run the indexer to extract product variants data + * + * @return void + * @throws \RuntimeException + */ + protected function runIndexer() : void + { + try { + $this->indexer->load(self::PRODUCT_VARIANT_FEED_INDEXER); + $this->indexer->reindexAll(); + } catch (\THrowable $e) { + throw new \RuntimeException('Could not reindex product variant data'); + } + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php b/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php new file mode 100644 index 000000000..57df85f56 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php @@ -0,0 +1,75 @@ +productRepository->get('configurable'); + $configurableId = $configurable->getId(); + $extractedVariantsData = $this->productVariantsFeed->getFeedByProductIds( + [$configurableId] + )['feed']; + $this->assertCount(2, $extractedVariantsData); + + $simple = $this->productRepository->get('simple_10'); + $this->deleteProduct($simple->getSku()); + $this->runIndexer(); + + $emptyVariantsData = $this->productVariantsFeed->getFeedByProductIds( + [$configurableId] + )['feed']; + $this->assertCount(1, $emptyVariantsData); + $deletedVariantsData = $this->productVariantsFeed->getDeletedByProductIds( + [$configurableId] + ); + $this->assertCount(1, $deletedVariantsData); + } catch (NoSuchEntityException $e) { + $this->fail('Expected product could not be retrieved'); + } catch (StateException $e) { + $this->fail('Product could not be deleted'); + } catch (\RuntimeException $e) { + $this->fail('Product variants failed to reindex'); + } + } + + /** + * Delete product variant + * + * @param string $productSku + * @throws StateException|NoSuchEntityException + */ + private function deleteProduct($productSku) : void + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $this->productRepository->deleteById($productSku); + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/composer.json b/app/code/Magento/ProductVariantDataExporter/composer.json new file mode 100644 index 000000000..b99a17192 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/composer.json @@ -0,0 +1,28 @@ +{ + "name": "magento/module-product-variant-data-exporter", + "description": "Product Variant Data Exporter", + "config": { + "sort-packages": true + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ProductVariantDataExporter\\": "" + } + }, + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0||~7.4.0", + "guzzlehttp/guzzle": "*", + "guzzlehttp/psr7": "~1.0", + "magento/framework": "*", + "magento/module-config": "*", + "magento/module-data-exporter": "*" + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/etc/db_schema.xml b/app/code/Magento/ProductVariantDataExporter/etc/db_schema.xml new file mode 100644 index 000000000..8b39b907c --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/db_schema.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + +
+
diff --git a/app/code/Magento/ProductVariantDataExporter/etc/db_schema_whitelist.json b/app/code/Magento/ProductVariantDataExporter/etc/db_schema_whitelist.json new file mode 100644 index 000000000..8765d4476 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/db_schema_whitelist.json @@ -0,0 +1,17 @@ +{ + "catalog_data_exporter_product_variants": { + "index": { + "CATALOG_DATA_EXPORTER_PRODUCT_VARIANTS_MODIFIED_AT": true + }, + "constraint": { + "PRIMARY": true + }, + "column": { + "parent_id": true, + "id": true, + "feed_data": true, + "modified_at": true, + "is_deleted": true + } + } +} diff --git a/app/code/Magento/ProductVariantDataExporter/etc/di.xml b/app/code/Magento/ProductVariantDataExporter/etc/di.xml new file mode 100644 index 000000000..2371528b9 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/di.xml @@ -0,0 +1,61 @@ + + + + + + variants + id + catalog_product_entity + entity_id + catalog_data_exporter_product_variants + id + parent_id + + feed_data + is_deleted + + + + + + + + id + parent_id + + + + + + + + + + + Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantFeedIndexMetadata + Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantDataSerializer + Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantIndexerCallbackInterface + + + + + + Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantFeedIndexMetadata + + + + + + + Magento\ProductVariantDataExporter\Model\ProductVariantFeed + + + + diff --git a/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml b/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml new file mode 100644 index 000000000..6cd463af8 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/ProductVariantDataExporter/etc/indexer.xml b/app/code/Magento/ProductVariantDataExporter/etc/indexer.xml new file mode 100644 index 000000000..774175ede --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/indexer.xml @@ -0,0 +1,20 @@ + + + + + Product Variant Feed + Collects data for a Product Variants Feed + + + + + diff --git a/app/code/Magento/ProductVariantDataExporter/etc/module.xml b/app/code/Magento/ProductVariantDataExporter/etc/module.xml new file mode 100644 index 000000000..f65a8cb59 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/ProductVariantDataExporter/etc/mview.xml b/app/code/Magento/ProductVariantDataExporter/etc/mview.xml new file mode 100644 index 000000000..83ad31221 --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/etc/mview.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/ProductVariantDataExporter/registration.php b/app/code/Magento/ProductVariantDataExporter/registration.php new file mode 100644 index 000000000..4ec8191be --- /dev/null +++ b/app/code/Magento/ProductVariantDataExporter/registration.php @@ -0,0 +1,9 @@ + Date: Thu, 5 Nov 2020 16:39:11 +0200 Subject: [PATCH 02/13] Product variants Minor syntax fixes --- .../Magento/CatalogExport/Model/ProductVariantRepository.php | 2 +- .../ConfigurableProductDataExporter/Plugin/ReindexVariants.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php index 99660dca7..fc70ace15 100644 --- a/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php +++ b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php @@ -15,7 +15,7 @@ use Psr\Log\LoggerInterface; /** - * @inheritdoc + * Product variant repository */ class ProductVariantRepository implements ProductVariantRepositoryInterface { diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php index edad7f9bf..b921f1d46 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php @@ -64,8 +64,7 @@ public function afterSave( ResourceProduct $result, AbstractModel $product ): ResourceProduct { - $type = $product->getTypeId(); - if ($type === Type::TYPE_SIMPLE || $type === Type::TYPE_VIRTUAL) { + if (\in_array($product->getTypeId(), [Type::TYPE_SIMPLE, Type::TYPE_VIRTUAL], true)) { $select = $this->linkedAttributesQuery->getQuery((int)$product->getId()); $connection = $this->resourceConnection->getConnection(); $configurableLinks = $connection->query($select)->fetchAll(); From e2e18be74b53bbd1e380383396de73623f32ab59 Mon Sep 17 00:00:00 2001 From: jekabs Date: Thu, 5 Nov 2020 17:26:41 +0200 Subject: [PATCH 03/13] Product variants -Changed entity type to string --- app/code/Magento/CatalogExport/Event/Data/Entity.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/CatalogExport/Event/Data/Entity.php b/app/code/Magento/CatalogExport/Event/Data/Entity.php index 0d27bd082..6a82a09b8 100644 --- a/app/code/Magento/CatalogExport/Event/Data/Entity.php +++ b/app/code/Magento/CatalogExport/Event/Data/Entity.php @@ -14,7 +14,7 @@ class Entity { /** - * @var int + * @var string */ private $entityId; @@ -26,9 +26,9 @@ class Entity /** * Get entity id. * - * @return int + * @return string */ - public function getEntityId(): int + public function getEntityId(): string { return $this->entityId; } @@ -36,11 +36,11 @@ public function getEntityId(): int /** * Set entity id. * - * @param int $entityId + * @param string $entityId * * @return void */ - public function setEntityId(int $entityId): void + public function setEntityId(string $entityId): void { $this->entityId = $entityId; } From 340993150c5ac03e0890fa2c9748c1202d63b1bc Mon Sep 17 00:00:00 2001 From: jekabs Date: Fri, 6 Nov 2020 13:00:14 +0200 Subject: [PATCH 04/13] Product variants -Test fixes --- .../Test/Integration/AbstractProductVariantsTest.php | 8 +++++--- .../Test/Integration/VariantsRemovalTest.php | 4 ++-- app/code/Magento/ProductVariantDataExporter/composer.json | 1 - .../Magento/ProductVariantDataExporter/etc/et_schema.xml | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/ProductVariantDataExporter/Test/Integration/AbstractProductVariantsTest.php b/app/code/Magento/ProductVariantDataExporter/Test/Integration/AbstractProductVariantsTest.php index b6065e338..6c24d8ffa 100644 --- a/app/code/Magento/ProductVariantDataExporter/Test/Integration/AbstractProductVariantsTest.php +++ b/app/code/Magento/ProductVariantDataExporter/Test/Integration/AbstractProductVariantsTest.php @@ -111,15 +111,17 @@ protected function setUp() : void /** * Run the indexer to extract product variants data * + * @param array $parentIds * @return void + * * @throws \RuntimeException */ - protected function runIndexer() : void + protected function runIndexer(array $parentIds) : void { try { $this->indexer->load(self::PRODUCT_VARIANT_FEED_INDEXER); - $this->indexer->reindexAll(); - } catch (\THrowable $e) { + $this->indexer->reindexList($parentIds); + } catch (\Throwable $e) { throw new \RuntimeException('Could not reindex product variant data'); } } diff --git a/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php b/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php index 57df85f56..a6b831fcc 100644 --- a/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php +++ b/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php @@ -37,7 +37,7 @@ public function testDeleteConfigurableProductVariants(): void $simple = $this->productRepository->get('simple_10'); $this->deleteProduct($simple->getSku()); - $this->runIndexer(); + $this->runIndexer([$configurableId]); $emptyVariantsData = $this->productVariantsFeed->getFeedByProductIds( [$configurableId] @@ -52,7 +52,7 @@ public function testDeleteConfigurableProductVariants(): void } catch (StateException $e) { $this->fail('Product could not be deleted'); } catch (\RuntimeException $e) { - $this->fail('Product variants failed to reindex'); + $this->fail($e->getMessage()); } } diff --git a/app/code/Magento/ProductVariantDataExporter/composer.json b/app/code/Magento/ProductVariantDataExporter/composer.json index b99a17192..db5517b00 100644 --- a/app/code/Magento/ProductVariantDataExporter/composer.json +++ b/app/code/Magento/ProductVariantDataExporter/composer.json @@ -22,7 +22,6 @@ "guzzlehttp/guzzle": "*", "guzzlehttp/psr7": "~1.0", "magento/framework": "*", - "magento/module-config": "*", "magento/module-data-exporter": "*" } } diff --git a/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml b/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml index 6cd463af8..0d7e5b7fb 100644 --- a/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml +++ b/app/code/Magento/ProductVariantDataExporter/etc/et_schema.xml @@ -18,6 +18,6 @@ - + From 54727420c6a631ac9da4966e5414ba724304593b Mon Sep 17 00:00:00 2001 From: jekabs Date: Fri, 6 Nov 2020 16:43:48 +0200 Subject: [PATCH 05/13] Product variants -Minor fixes --- .../Product/ProductVariants/ConfigurableId.php | 16 +++++----------- .../Model/Provider/Product/Variants.php | 15 ++++++++------- .../Query/LinkedAttributesQuery.php | 2 +- ...ProductVariantQuery.php => VariantsQuery.php} | 4 ++-- .../Plugin/ReindexVariants.php | 2 +- .../DataExporter/Model/Indexer/FeedIndexer.php | 6 +++--- .../Model/Indexer/ProductVariantFeedIndexer.php | 10 +++++----- .../Provider/ProductVariants/IdInterface.php | 5 ++--- 8 files changed, 27 insertions(+), 33 deletions(-) rename app/code/Magento/ConfigurableProductDataExporter/{Plugin => Model}/Query/LinkedAttributesQuery.php (96%) rename app/code/Magento/ConfigurableProductDataExporter/Model/Query/{ProductVariantQuery.php => VariantsQuery.php} (98%) diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php index b36ebdb0f..67df74fce 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php @@ -11,25 +11,19 @@ use Magento\ConfigurableProductDataExporter\Model\Provider\Product\ConfigurableOptionValueUid; /** - * Create configurable product variant id in base64 encode + * Create configurable product variant id */ class ConfigurableId implements IdInterface { /** * Returns uid based on parent and child product ids * - * @param string $parentId - * @param string $childId + * @param string[] $params * @return string */ - public function resolve(string $parentId, string $childId): string + public function resolve(string ...$params): string { - $uid = [ - ConfigurableOptionValueUid::OPTION_TYPE, - $parentId, - $childId - ]; - - return implode('/', $uid); + array_unshift($params, ConfigurableOptionValueUid::OPTION_TYPE); + return implode('/', $params); } } diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php index 687aa8384..aca719365 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php @@ -7,13 +7,14 @@ namespace Magento\ConfigurableProductDataExporter\Model\Provider\Product; -use Magento\ConfigurableProductDataExporter\Model\Query\ProductVariantQuery; +use Magento\ConfigurableProductDataExporter\Model\Query\VariantsQuery; use Magento\DataExporter\Exception\UnableRetrieveData; use Magento\Framework\App\ResourceConnection; use Psr\Log\LoggerInterface; /** * Configurable product variant data provider + * TODO: Deprecated. Remove this class and its query class. */ class Variants { @@ -23,9 +24,9 @@ class Variants private $resourceConnection; /** - * @var ProductVariantQuery + * @var VariantsQuery */ - private $productVariantQuery; + private $variantQuery; /** * @var LoggerInterface @@ -35,16 +36,16 @@ class Variants /** * Variants constructor. * @param ResourceConnection $resourceConnection - * @param ProductVariantQuery $productVariantQuery + * @param VariantsQuery $variantQuery * @param LoggerInterface $logger */ public function __construct( ResourceConnection $resourceConnection, - ProductVariantQuery $productVariantQuery, + VariantsQuery $variantQuery, LoggerInterface $logger ) { $this->resourceConnection = $resourceConnection; - $this->productVariantQuery = $productVariantQuery; + $this->variantQuery = $variantQuery; $this->logger = $logger; } @@ -65,7 +66,7 @@ public function get(array $values) : array $queryArguments['productId'][$value['productId']] = $value['productId']; $queryArguments['storeViewCode'][$value['storeViewCode']] = $value['storeViewCode']; } - $select = $this->productVariantQuery->getQuery($queryArguments); + $select = $this->variantQuery->getQuery($queryArguments); $cursor = $connection->query($select); while ($row = $cursor->fetch()) { $key = $row['sku'] . '-' . $row['storeViewCode']; diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php similarity index 96% rename from app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php rename to app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php index f98f3b7bc..50fa1a45e 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Plugin/Query/LinkedAttributesQuery.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\ConfigurableProductDataExporter\Plugin\Query; +namespace Magento\ConfigurableProductDataExporter\Query; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Query/ProductVariantQuery.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/VariantsQuery.php similarity index 98% rename from app/code/Magento/ConfigurableProductDataExporter/Model/Query/ProductVariantQuery.php rename to app/code/Magento/ConfigurableProductDataExporter/Model/Query/VariantsQuery.php index 1fcaffe39..fe447ff3e 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Query/ProductVariantQuery.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/VariantsQuery.php @@ -13,7 +13,7 @@ /** * Product variant query builder */ -class ProductVariantQuery +class VariantsQuery { /** * @var ResourceConnection @@ -21,7 +21,7 @@ class ProductVariantQuery private $resourceConnection; /** - * ProductVariantQuery constructor. + * VariantsQuery constructor. * * @param ResourceConnection $resourceConnection */ diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php index b921f1d46..8540f00d7 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php @@ -9,7 +9,7 @@ use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\ResourceModel\Product as ResourceProduct; -use Magento\ConfigurableProductDataExporter\Plugin\Query\LinkedAttributesQuery; +use Magento\ConfigurableProductDataExporter\Query\LinkedAttributesQuery; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Model\AbstractModel; use Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantFeedIndexer; diff --git a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php index 3d4c3e4f2..382f409ed 100644 --- a/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php +++ b/app/code/Magento/DataExporter/Model/Indexer/FeedIndexer.php @@ -87,7 +87,7 @@ public function __construct( * @param int $lastKnownId * @return Select */ - protected function getIdsSelect(int $lastKnownId) : Select + private function getIdsSelect(int $lastKnownId) : Select { $columnExpression = sprintf('s.%s', $this->feedIndexMetadata->getSourceTableField()); $whereClause = sprintf('s.%s > ?', $this->feedIndexMetadata->getSourceTableField()); @@ -219,7 +219,7 @@ private function prepareIndexData(array $indexData): array * * @return void */ - protected function process($indexData = []) : void + private function process($indexData = []) : void { $feedIdentity = $this->feedIndexMetadata->getFeedIdentity(); $data = $this->processor->process($this->feedIndexMetadata->getFeedName(), $indexData); @@ -317,7 +317,7 @@ private function fetchFeedData(array $ids): array * @param array $ids * @return void */ - protected function markRemoved(array $ids) : void + private function markRemoved(array $ids) : void { $connection = $this->resourceConnection->getConnection(); $select = $connection->select() diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php index c7cbcb14e..8931f4902 100644 --- a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php +++ b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php @@ -21,7 +21,7 @@ class ProductVariantFeedIndexer extends FeedIndexer * @param int $lastKnownId * @return Select */ - protected function getIdsSelect(int $lastKnownId): Select + private function getIdsSelect(int $lastKnownId): Select { $sourceTableField = $this->feedIndexMetadata->getSourceTableField(); $columnExpression = sprintf( @@ -32,7 +32,7 @@ protected function getIdsSelect(int $lastKnownId): Select $connection = $this->resourceConnection->getConnection(); return $connection->select() ->from( - ['s' => $this->feedIndexMetadata->getSourceTableName()], + ['s' => $this->resourceConnection->getTableName($this->feedIndexMetadata->getSourceTableName())], [ $this->feedIndexMetadata->getFeedTableParentField() => 's.' . $sourceTableField ] @@ -47,7 +47,7 @@ protected function getIdsSelect(int $lastKnownId): Select * * @return \Generator */ - protected function getAllIds(): ?\Generator + private function getAllIds(): ?\Generator { $connection = $this->resourceConnection->getConnection(); $lastKnownId = 0; @@ -128,7 +128,7 @@ public function execute($ids): void * @param array $indexData * @return void */ - protected function process($indexData = []): void + private function process($indexData = []): void { $feedIdentity = $this->feedIndexMetadata->getFeedIdentity(); $data = $this->processor->process($this->feedIndexMetadata->getFeedName(), $indexData); @@ -177,7 +177,7 @@ protected function fetchFeedDataIds(array $ids): array * @param array $ids * @return void */ - protected function markRemoved(array $ids): void + private function markRemoved(array $ids): void { $connection = $this->resourceConnection->getConnection(); $connection->update( diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php index 74c975542..a5b80e3e8 100644 --- a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php @@ -15,9 +15,8 @@ interface IdInterface /** * Get product variant id * - * @param string $parentId - * @param string $childId + * @param string[] $params * @return string */ - public function resolve(string $parentId, string $childId) : string; + public function resolve(string ...$params) : string; } From b24c4a5809eb10b171a86386fa9bd86572ea54f9 Mon Sep 17 00:00:00 2001 From: jekabs Date: Fri, 6 Nov 2020 17:35:07 +0200 Subject: [PATCH 06/13] Product variants -Moved test from variants to configurable variants and other minor adjustments --- .../Model/Query/LinkedAttributesQuery.php | 2 +- .../Plugin/ReindexVariants.php | 2 +- .../ConfigurableProductVariantsTest.php | 67 +++++++++++++++-- .../Test/Integration/VariantsRemovalTest.php | 75 ------------------- 4 files changed, 64 insertions(+), 82 deletions(-) delete mode 100644 app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php index 50fa1a45e..083f1cadf 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Query/LinkedAttributesQuery.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\ConfigurableProductDataExporter\Query; +namespace Magento\ConfigurableProductDataExporter\Model\Query; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; diff --git a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php index 8540f00d7..e8b2c2774 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Plugin/ReindexVariants.php @@ -9,7 +9,7 @@ use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\ResourceModel\Product as ResourceProduct; -use Magento\ConfigurableProductDataExporter\Query\LinkedAttributesQuery; +use Magento\ConfigurableProductDataExporter\Model\Query\LinkedAttributesQuery; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Model\AbstractModel; use Magento\ProductVariantDataExporter\Model\Indexer\ProductVariantFeedIndexer; diff --git a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php index 16dd6c475..acde1f0f0 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductVariantsTest.php @@ -7,9 +7,9 @@ namespace Magento\ConfigurableProductDataExporter\Test\Integration; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\ProductVariantDataExporter\Test\Integration\AbstractProductVariantsTest; -use Magento\Framework\Exception\NoSuchEntityException; /** * Test class for configurable product variants export @@ -40,19 +40,76 @@ public function testConfigurableVariants(): void $diff = $this->arrayUtils->recursiveDiff($expected, $actual); self::assertEquals([], $diff, "Product variants response doesn't equal expected response"); - } catch (NoSuchEntityException $e) { - $this->fail('Test products could not be retrieved'); + } catch (\Throwable $e) { + $this->fail($e->getMessage()); } } + /** + * Test that variants are deleted as expected. + * + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * + * @return void + */ + public function testDeleteConfigurableProductVariants(): void + { + try { + $configurable = $this->productRepository->get('configurable'); + $configurableId = $configurable->getId(); + $extractedVariantsData = $this->productVariantsFeed->getFeedByProductIds( + [$configurableId] + )['feed']; + $this->assertCount(2, $extractedVariantsData); + + $simple = $this->productRepository->get('simple_10'); + $this->deleteProduct($simple->getSku()); + $this->runIndexer([$configurableId]); + + $emptyVariantsData = $this->productVariantsFeed->getFeedByProductIds( + [$configurableId] + )['feed']; + $this->assertCount(1, $emptyVariantsData); + $deletedVariantsData = $this->productVariantsFeed->getDeletedByProductIds( + [$configurableId] + ); + $this->assertCount(1, $deletedVariantsData); + } catch (\Throwable $e) { + $this->fail($e->getMessage()); + } + } + + /** + * Delete product variant + * + * @param string $productSku + * @throws \RuntimeException + */ + private function deleteProduct(string $productSku): void + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + try { + $this->productRepository->deleteById($productSku); + } catch (\Throwable $e) { + throw new \RuntimeException('Could not delete product ' . $productSku); + } + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } + /** * Get the expected variants for the first combination of options being tested. * - * @param Product $configurable + * @param ProductInterface $configurable * @param Product[] $simples * @return array */ - private function getExpectedProductVariants(Product $configurable, array $simples): array + private function getExpectedProductVariants(ProductInterface $configurable, array $simples): array { $configurableOptions = $configurable->getExtensionAttributes()->getConfigurableProductOptions(); $variants = []; diff --git a/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php b/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php deleted file mode 100644 index a6b831fcc..000000000 --- a/app/code/Magento/ProductVariantDataExporter/Test/Integration/VariantsRemovalTest.php +++ /dev/null @@ -1,75 +0,0 @@ -productRepository->get('configurable'); - $configurableId = $configurable->getId(); - $extractedVariantsData = $this->productVariantsFeed->getFeedByProductIds( - [$configurableId] - )['feed']; - $this->assertCount(2, $extractedVariantsData); - - $simple = $this->productRepository->get('simple_10'); - $this->deleteProduct($simple->getSku()); - $this->runIndexer([$configurableId]); - - $emptyVariantsData = $this->productVariantsFeed->getFeedByProductIds( - [$configurableId] - )['feed']; - $this->assertCount(1, $emptyVariantsData); - $deletedVariantsData = $this->productVariantsFeed->getDeletedByProductIds( - [$configurableId] - ); - $this->assertCount(1, $deletedVariantsData); - } catch (NoSuchEntityException $e) { - $this->fail('Expected product could not be retrieved'); - } catch (StateException $e) { - $this->fail('Product could not be deleted'); - } catch (\RuntimeException $e) { - $this->fail($e->getMessage()); - } - } - - /** - * Delete product variant - * - * @param string $productSku - * @throws StateException|NoSuchEntityException - */ - private function deleteProduct($productSku) : void - { - $this->registry->unregister('isSecureArea'); - $this->registry->register('isSecureArea', true); - - $this->productRepository->deleteById($productSku); - - $this->registry->unregister('isSecureArea'); - $this->registry->register('isSecureArea', false); - } -} From d7dc7a9b6e749722e45dcf46b3503061918fedf4 Mon Sep 17 00:00:00 2001 From: jekabs Date: Mon, 9 Nov 2020 10:37:12 +0200 Subject: [PATCH 07/13] Product variants --- .../Model/ProductVariantRepository.php | 2 +- .../Test/Api/ProductVariantExportTest.php | 48 ++++++++----------- .../Model/Provider/ProductVariants.php | 1 + 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php index fc70ace15..369c4c421 100644 --- a/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php +++ b/app/code/Magento/CatalogExport/Model/ProductVariantRepository.php @@ -15,7 +15,7 @@ use Psr\Log\LoggerInterface; /** - * Product variant repository + * Product variant entity repository */ class ProductVariantRepository implements ProductVariantRepositoryInterface { diff --git a/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php b/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php index 63c03255f..d282699f1 100644 --- a/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php +++ b/app/code/Magento/CatalogExport/Test/Api/ProductVariantExportTest.php @@ -71,32 +71,29 @@ protected function setUp(): void * * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes.php * @return void + * @throws NoSuchEntityException */ public function testGetVariantById(): void { $this->_markTestAsRestOnly('SOAP will be covered in another test'); - try { - $configurable = $this->productRepository->get('configurable'); - $configurableId = $configurable->getId(); - $simple = $this->productRepository->get('simple_10'); - $simpleId = $simple->getId(); - $variantId = \base64_encode(\sprintf( - 'configurable/%1$s/%2$s', - $configurableId, - $simpleId, - )); - $this->createServiceInfo['get']['rest']['resourcePath'] .= '?ids[0]=' . $variantId; - $apiResult = $this->_webApiCall($this->createServiceInfo['get'], []); - $variantFeed = $this->productVariantsFeed->getFeedByIds([$variantId])['feed']; - - foreach (array_keys($variantFeed) as $feedKey) { - unset($variantFeed[$feedKey]['modifiedAt'], $variantFeed[$feedKey]['deleted']); - } - - $this->assertVariantsEqual($variantFeed, $apiResult); - } catch (NoSuchEntityException $e) { - $this->fail('Expected product could not be retrieved'); + $configurable = $this->productRepository->get('configurable'); + $configurableId = $configurable->getId(); + $simple = $this->productRepository->get('simple_10'); + $simpleId = $simple->getId(); + $variantId = \base64_encode(\sprintf( + 'configurable/%1$s/%2$s', + $configurableId, + $simpleId, + )); + $this->createServiceInfo['get']['rest']['resourcePath'] .= '?ids[0]=' . $variantId; + $apiResult = $this->_webApiCall($this->createServiceInfo['get'], []); + $variantFeed = $this->productVariantsFeed->getFeedByIds([$variantId])['feed']; + + foreach (array_keys($variantFeed) as $feedKey) { + unset($variantFeed[$feedKey]['modifiedAt'], $variantFeed[$feedKey]['deleted']); } + + $this->assertVariantsEqual($variantFeed, $apiResult); } /** @@ -104,17 +101,14 @@ public function testGetVariantById(): void * * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_products_with_two_attributes.php * @return void + * @throws NoSuchEntityException */ public function testGetVariantByProductId(): void { $this->_markTestAsRestOnly('SOAP will be covered in another test'); - try { - $product = $this->productRepository->get('configurable'); - $productId = $product->getId(); - } catch (NoSuchEntityException $e) { - $this->fail('Expected product could not be retrieved'); - } + $product = $this->productRepository->get('configurable'); + $productId = $product->getId(); $this->createServiceInfo['getByProductIds']['rest']['resourcePath'] .= '?productIds[0]=' . $productId; $apiResult = $this->_webApiCall($this->createServiceInfo['getByProductIds'], []); diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants.php index 10c01c344..99d23beee 100644 --- a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants.php +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants.php @@ -30,6 +30,7 @@ public function __construct( /** * @inheritdoc + * * @throws UnableRetrieveData */ public function get(array $values): array From b2102bfb2895501f5b849b6313644ac3303f2166 Mon Sep 17 00:00:00 2001 From: jekabs Date: Mon, 9 Nov 2020 16:10:24 +0200 Subject: [PATCH 08/13] Product variants --- .../Model/Indexer/ProductVariantFeedIndexer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php index 8931f4902..65aba3ecc 100644 --- a/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php +++ b/app/code/Magento/ProductVariantDataExporter/Model/Indexer/ProductVariantFeedIndexer.php @@ -71,7 +71,6 @@ private function getAllIds(): ?\Generator */ public function executeFull(): void { - $this->truncateFeedTable(); foreach ($this->getAllIds() as $ids) { $this->process($ids); } From 92f92867548d66450e4dc38e933336edddbd1ce1 Mon Sep 17 00:00:00 2001 From: jekabs Date: Tue, 10 Nov 2020 18:19:09 +0200 Subject: [PATCH 09/13] Product variants -Comment fix -Made product variant id provider params more defined --- .../Provider/Product/ProductVariants.php | 8 ++++-- .../ProductVariants/ConfigurableId.php | 28 +++++++++++++++++-- .../Model/Provider/Product/Variants.php | 2 +- .../etc/et_schema.xml | 2 +- .../Provider/ProductVariants/IdInterface.php | 3 +- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php index 526ed9911..12fd010f4 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants.php @@ -7,6 +7,7 @@ namespace Magento\ConfigurableProductDataExporter\Model\Provider\Product; +use Magento\ConfigurableProductDataExporter\Model\Provider\Product\ProductVariants\ConfigurableId; use Magento\ProductVariantDataExporter\Model\Provider\ProductVariants\IdFactory; use Magento\ProductVariantDataExporter\Model\Provider\ProductVariants\OptionValueFactory; use Magento\ProductVariantDataExporter\Model\Provider\ProductVariantsProviderInterface; @@ -112,7 +113,7 @@ public function get(array $values): array */ private function getVariants(array $parentIds): array { - $variants = []; + $variants = []; $idResolver = $this->idFactory->get('configurable'); $optionValueResolver = $this->optionValueFactory->get('configurable'); @@ -120,7 +121,10 @@ private function getVariants(array $parentIds): array $this->variantsOptionValuesQuery->getQuery($parentIds) ); while ($row = $cursor->fetch()) { - $id = $idResolver->resolve($row['parentId'], $row['childId']); + $id = $idResolver->resolve([ + ConfigurableId::PARENT_ID_KEY => $row['parentId'], + ConfigurableId::CHILD_ID_KEY => $row['childId'] + ]); $optionValueUid = ($this->optionValueUid->resolve( $row['attributeId'], $row['attributeValue'] diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php index 67df74fce..084abd014 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/ProductVariants/ConfigurableId.php @@ -15,15 +15,37 @@ */ class ConfigurableId implements IdInterface { + /** + * Product variant configurable id child key. + */ + public const CHILD_ID_KEY = 'childId'; + + /** + * Product variant configurable id parent key. + */ + public const PARENT_ID_KEY = 'parentId'; + /** * Returns uid based on parent and child product ids * * @param string[] $params * @return string + * @throws \InvalidArgumentException */ - public function resolve(string ...$params): string + public function resolve(array $params): string { - array_unshift($params, ConfigurableOptionValueUid::OPTION_TYPE); - return implode('/', $params); + if (!isset($params[self::CHILD_ID_KEY], $params[self::PARENT_ID_KEY])) { + throw new \InvalidArgumentException( + 'Cannot generate configurable id, because parent or child id is missing' + ); + } + + $uid = [ + ConfigurableOptionValueUid::OPTION_TYPE, + $params[self::PARENT_ID_KEY], + $params[self::CHILD_ID_KEY] + ]; + + return implode('/', $uid); } } diff --git a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php index aca719365..b3a2a3818 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Model/Provider/Product/Variants.php @@ -14,7 +14,7 @@ /** * Configurable product variant data provider - * TODO: Deprecated. Remove this class and its query class. + * TODO: Deprecated - remove this class and its query. https://github.com/magento/catalog-storefront/issues/419 */ class Variants { diff --git a/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml b/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml index 7e117c0f2..fe8cb5950 100644 --- a/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml +++ b/app/code/Magento/ConfigurableProductDataExporter/etc/et_schema.xml @@ -14,7 +14,7 @@ - + diff --git a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php index a5b80e3e8..9941b10c3 100644 --- a/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php +++ b/app/code/Magento/ProductVariantDataExporter/Model/Provider/ProductVariants/IdInterface.php @@ -17,6 +17,7 @@ interface IdInterface * * @param string[] $params * @return string + * @throws \InvalidArgumentException */ - public function resolve(string ...$params) : string; + public function resolve(array $params) : string; } From d4b7728acae05d5050cc4ac18e0125cf35eb24df Mon Sep 17 00:00:00 2001 From: ruslankostiv Date: Tue, 10 Nov 2020 15:15:08 -0600 Subject: [PATCH 10/13] 27: Product variants --- .../_files/blacklist/composer_root_modules_data_export.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt index a3a5b56bf..b2bedf578 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt @@ -6,4 +6,5 @@ magento/module-configurable-product-data-exporter magento/module-bundle-product-data-exporter magento/module-catalog-export magento/module-catalog-export-api -magento/module-parent-product-data-exporter \ No newline at end of file +magento/module-parent-product-data-exporter +magento/module-product-variant-data-exporter \ No newline at end of file From 65206598e1bffd06835efdeca0c4c5290fae38cb Mon Sep 17 00:00:00 2001 From: ruslankostiv Date: Fri, 13 Nov 2020 09:41:20 -0600 Subject: [PATCH 11/13] 27: Product variants --- .../Test/Integration/ConfigurableProductsTest.php | 14 ++++++++++++-- .../ProductVariantDataExporter/composer.json | 3 ++- .../composer_root_modules_data_export.txt | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php index 27c556f85..f76957a8e 100755 --- a/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php +++ b/app/code/Magento/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php @@ -197,8 +197,18 @@ private function validateVariantsData(ProductInterface $product, array $extract, ]; } $actualVariants = $extract['feedData']['variants']; - usort($actualVariants, function ($a, $b){return $a['sku'] <=> $b['sku'];}); - usort($variants, function ($a, $b){return $a['sku'] <=> $b['sku'];}); + \usort( + $actualVariants, + function ($a, $b) { + return $a['sku'] <=> $b['sku']; + } + ); + \usort( + $variants, + function ($a, $b) { + return $a['sku'] <=> $b['sku']; + } + ); $this->assertEquals($variants, $actualVariants); } diff --git a/app/code/Magento/ProductVariantDataExporter/composer.json b/app/code/Magento/ProductVariantDataExporter/composer.json index db5517b00..ac698cb3b 100644 --- a/app/code/Magento/ProductVariantDataExporter/composer.json +++ b/app/code/Magento/ProductVariantDataExporter/composer.json @@ -22,6 +22,7 @@ "guzzlehttp/guzzle": "*", "guzzlehttp/psr7": "~1.0", "magento/framework": "*", - "magento/module-data-exporter": "*" + "magento/module-data-exporter": "*", + "magento/module-catalog": "*" } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt index b2bedf578..9e1376f0e 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_data_export.txt @@ -7,4 +7,4 @@ magento/module-bundle-product-data-exporter magento/module-catalog-export magento/module-catalog-export-api magento/module-parent-product-data-exporter -magento/module-product-variant-data-exporter \ No newline at end of file +magento/module-product-variant-data-exporter From 7e1dbc474385ff52af4bdf58991886aa70f98ce2 Mon Sep 17 00:00:00 2001 From: ruslankostiv Date: Fri, 13 Nov 2020 10:29:40 -0600 Subject: [PATCH 12/13] 27: Product variants --- app/code/Magento/CatalogDataExporter/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogDataExporter/composer.json b/app/code/Magento/CatalogDataExporter/composer.json index e8a4d037d..613bf1d0e 100644 --- a/app/code/Magento/CatalogDataExporter/composer.json +++ b/app/code/Magento/CatalogDataExporter/composer.json @@ -26,6 +26,7 @@ "magento/module-directory": "*", "magento/module-data-exporter": "*", "magento/module-customer": "*", - "magento/module-downloadable": "*" + "magento/module-downloadable": "*", + "magento/module-indexer": "*" } } From c8d3f0d83c5c6a13e1a5dd6be72b75d4f583459b Mon Sep 17 00:00:00 2001 From: ruslankostiv Date: Fri, 13 Nov 2020 11:04:48 -0600 Subject: [PATCH 13/13] 27: Product variants --- app/code/Magento/ProductVariantDataExporter/composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/ProductVariantDataExporter/composer.json b/app/code/Magento/ProductVariantDataExporter/composer.json index ac698cb3b..db5517b00 100644 --- a/app/code/Magento/ProductVariantDataExporter/composer.json +++ b/app/code/Magento/ProductVariantDataExporter/composer.json @@ -22,7 +22,6 @@ "guzzlehttp/guzzle": "*", "guzzlehttp/psr7": "~1.0", "magento/framework": "*", - "magento/module-data-exporter": "*", - "magento/module-catalog": "*" + "magento/module-data-exporter": "*" } }