From fa6782f3a22f1185ebd4e71e915962f2a57c199f Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Wed, 13 Dec 2017 13:52:25 +0200 Subject: [PATCH 01/11] 6486: magento/magento2#6486: Unable to save certain product properties via Rest API --- .../Catalog/Model/ProductRepository.php | 3 +++ .../Api/ProductRepositoryInterfaceTest.php | 26 +++++++++++++++++++ .../Catalog/Model/ProductRepositoryTest.php | 21 +++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 83feea903f993..0ecbad55d74d9 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -329,6 +329,9 @@ protected function initializeProductData(array $productData, $createNew) unset($productData['media_gallery']); if ($createNew) { $product = $this->productFactory->create(); + if (!isset($productData['product_type'])) { + $product->setTypeId(Product\Type::TYPE_SIMPLE); + } if ($this->storeManager->hasSingleStore()) { $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index cb33edce3af39..7b7ec5e628d88 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -303,6 +303,32 @@ public function testCreateInvalidPriceFormat() } } + /** + * Test that Product Repository can correctly create simple product, if product type not specified in request. + * + * @return void + */ + public function testCreateWithoutSpecifiedType() + { + $price = 3.62; + $weight = 12.2; + $sku = 'simple_product_without_specified_type'; + $product = [ + 'sku' => '' . $sku . '', + 'name' => 'Simple Product Without Specified Type', + 'price' => $price, + 'weight' => $weight, + 'attribute_set_id' => 4, + ]; + $response = $this->saveProduct($product); + $this->assertSame($sku, $response[ProductInterface::SKU]); + $this->assertSame(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, $response[ProductInterface::TYPE_ID]); + $this->assertSame($price, $response[ProductInterface::PRICE]); + $this->assertSame($weight, $response[ProductInterface::WEIGHT]); + //Clean up. + $this->deleteProduct($product[ProductInterface::SKU]); + } + /** * @param array $fixtureProduct * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php index 9518e9c0cdf4f..330487b757f61 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php @@ -49,4 +49,25 @@ public function testUpdateProductSku() $updatedProduct->load($productId); self::assertSame($newSku, $updatedProduct->getSku()); } + + /** + * Check Product Repository able to correctly create product without specified type. + * + * @magentoDbIsolation enabled + */ + public function testCreateWithoutSpecifiedType() + { + /** @var Product $product */ + $product = Bootstrap::getObjectManager()->get(ProductFactory::class)->create(); + $product->setName('Simple without specified type'); + $product->setSku('simple_without_specified_type'); + $product->setPrice(1.12); + $product->setWeight(1.23); + $product->setAttributeSetId(4); + $product = $this->productRepository->save($product); + + self::assertSame('1.1200', $product->getPrice()); + self::assertSame('1.2300', $product->getWeight()); + self::assertSame('simple', $product->getTypeId()); + } } From 9db02736120ce911f670e2a1f31efef5435aca7c Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Fri, 15 Dec 2017 10:31:57 +0200 Subject: [PATCH 02/11] 6486: magento/magento2#6486: Unable to save certain product properties via Rest API --- .../Magento/Catalog/Api/ProductRepositoryInterfaceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 7b7ec5e628d88..13e0c4697cc94 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -314,7 +314,7 @@ public function testCreateWithoutSpecifiedType() $weight = 12.2; $sku = 'simple_product_without_specified_type'; $product = [ - 'sku' => '' . $sku . '', + 'sku' => $sku, 'name' => 'Simple Product Without Specified Type', 'price' => $price, 'weight' => $weight, From 1d37cce45ac025929277dcdbeb44f1889564b23a Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Thu, 28 Dec 2017 11:38:22 +0200 Subject: [PATCH 03/11] 6486: magento/magento2#6486: Unable to save certain product properties via Rest API --- app/code/Magento/Catalog/Model/ProductRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 0ecbad55d74d9..fbf4a56206760 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -329,7 +329,7 @@ protected function initializeProductData(array $productData, $createNew) unset($productData['media_gallery']); if ($createNew) { $product = $this->productFactory->create(); - if (!isset($productData['product_type'])) { + if (isset($productData['price']) && !isset($productData['product_type'])) { $product->setTypeId(Product\Type::TYPE_SIMPLE); } if ($this->storeManager->hasSingleStore()) { From 2c1d6a4d115f1e97787349849d215e6c73ac1335 Mon Sep 17 00:00:00 2001 From: Danny Verkade Date: Tue, 2 Jan 2018 12:09:26 +0100 Subject: [PATCH 04/11] - Removed restore_quote event observer, which caused Magento to revert stock twice. --- app/code/Magento/CatalogInventory/etc/events.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 0a9f3c2d40dca..3197501e9b70b 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -27,9 +27,6 @@ - - - From 9ab75baec089c4a327a0c3b082f0aaedfbfb15ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torben=20Ho=CC=88hn?= Date: Fri, 5 Jan 2018 16:36:04 +0100 Subject: [PATCH 05/11] updated newsletter subscriber model --- .../Magento/Newsletter/Model/Subscriber.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index b8d7ccf83af7c..961ed539815e9 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -604,14 +604,19 @@ protected function _updateCustomerSubscription($customerId, $subscribe) $this->save(); $sendSubscription = $sendInformationEmail; - if ($sendSubscription === null xor $sendSubscription) { + if ($sendSubscription === null xor $sendSubscription && $this->isStatusChanged()) { try { - if ($isConfirmNeed) { - $this->sendConfirmationRequestEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_UNSUBSCRIBED) { - $this->sendUnsubscriptionEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_SUBSCRIBED) { - $this->sendConfirmationSuccessEmail(); + switch ($status) { + case self::STATUS_UNSUBSCRIBED: + $this->sendUnsubscriptionEmail(); + break; + case self::STATUS_SUBSCRIBED: + $this->sendConfirmationSuccessEmail(); + break; + case self::STATUS_NOT_ACTIVE: + if ($isConfirmNeed) + $this->sendConfirmationRequestEmail(); + break; } } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored From 149d08fbae7a0c747e816fb47752581486c27677 Mon Sep 17 00:00:00 2001 From: Bhargav Mehta Date: Sun, 7 Jan 2018 13:10:18 +0530 Subject: [PATCH 06/11] Fix 12221 --- .../GoogleAnalytics/view/frontend/web/js/google-analytics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js index 324881cdc5028..4f6f3e9ae88a0 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js +++ b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js @@ -54,7 +54,7 @@ define([ ga('send', 'pageview' + config.pageTrackingData.optPageUrl); // Process orders data - if (config.ordersTrackingData) { + if (config.ordersTrackingData.length) { ga('require', 'ec', 'ec.js'); //Set currency code From 843bc6229ba141cd1f326eeb90013c342c2d2983 Mon Sep 17 00:00:00 2001 From: Vinay Shah Date: Sun, 7 Jan 2018 14:12:04 +0530 Subject: [PATCH 07/11] magento/magento2#12705: Integrity constraint violation error after reordering product with custom options - Fixed issue by updating options if parent item is same for more than one product - Fixed issue for reorder in admin while checking the availability of product, which is not available in case of associate product. --- .../Model/Order/Reorder/OrderedProductAvailabilityChecker.php | 2 +- app/code/Magento/Quote/Model/Quote/Item.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index dceb5767edae9..45874494589dc 100644 --- a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php +++ b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php @@ -45,7 +45,7 @@ public function __construct( public function isAvailable(Item $item) { $buyRequest = $item->getBuyRequest(); - $superAttribute = $buyRequest->getData()['super_attribute']; + $superAttribute = isset($buyRequest->getData()['super_attribute']) ? $buyRequest->getData()['super_attribute'] : []; $connection = $this->getConnection(); $select = $connection->select(); $orderItemParentId = $item->getParentItem()->getProductId(); diff --git a/app/code/Magento/Quote/Model/Quote/Item.php b/app/code/Magento/Quote/Model/Quote/Item.php index d8177ddfe5236..fe6d712500bcd 100644 --- a/app/code/Magento/Quote/Model/Quote/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Item.php @@ -745,6 +745,9 @@ public function saveItemOptions() unset($this->_options[$index]); unset($this->_optionsByCode[$option->getCode()]); } else { + if (!$option->getItem() || !$option->getItem()->getId()) { + $option->setItem($this); + } $option->save(); } } From df96b51224f928cde3919cf6a69ca8c4236afc86 Mon Sep 17 00:00:00 2001 From: Vinay Shah Date: Sun, 7 Jan 2018 21:36:50 +0530 Subject: [PATCH 08/11] magento/magento2#12705: Integrity constraint violation error after reordering product with custom options - Fixed issue by updating options if parent item is same for more than one product - Fixed issue for reorder in admin while checking the availability of product, which is not available in case of associate product. - Fixed Null coalescing operator issue --- .../Model/Order/Reorder/OrderedProductAvailabilityChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index 45874494589dc..42d7d91fb90e8 100644 --- a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php +++ b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php @@ -45,7 +45,7 @@ public function __construct( public function isAvailable(Item $item) { $buyRequest = $item->getBuyRequest(); - $superAttribute = isset($buyRequest->getData()['super_attribute']) ? $buyRequest->getData()['super_attribute'] : []; + $superAttribute = $buyRequest->getData()['super_attribute'] ?? []; $connection = $this->getConnection(); $select = $connection->select(); $orderItemParentId = $item->getParentItem()->getProductId(); From e851948a93f63887f053559802b6de1348db4a56 Mon Sep 17 00:00:00 2001 From: Bhargav Mehta Date: Mon, 8 Jan 2018 17:26:35 +0530 Subject: [PATCH 09/11] Updated code to make it more efficient. --- .../GoogleAnalytics/view/frontend/web/js/google-analytics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js index 4f6f3e9ae88a0..cd6292b39e989 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js +++ b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js @@ -51,7 +51,6 @@ define([ if (config.pageTrackingData.isAnonymizedIpActive) { ga('set', 'anonymizeIp', true); } - ga('send', 'pageview' + config.pageTrackingData.optPageUrl); // Process orders data if (config.ordersTrackingData.length) { @@ -75,6 +74,9 @@ define([ } ga('send', 'pageview'); + }else{ + // Process Data if not orders + ga('send', 'pageview' + config.pageTrackingData.optPageUrl); } } } From d132c7ec62baccae9ee5ac4272eba2a7d9fdc8e8 Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Tue, 9 Jan 2018 16:07:12 +0200 Subject: [PATCH 10/11] 6486: magento/magento2#6486: Unable to save certain product properties via Rest API. Decrease Product Repository overall complexity. --- .../Catalog/Model/ProductRepository.php | 183 +------------- .../MediaGalleryProcessor.php | 218 +++++++++++++++++ .../MediaGalleryProcessorTest.php | 227 ++++++++++++++++++ .../Test/Unit/Model/ProductRepositoryTest.php | 101 ++------ 4 files changed, 478 insertions(+), 251 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index fbf4a56206760..f260b01c02ef4 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -6,10 +6,8 @@ */ namespace Magento\Catalog\Model; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; @@ -18,10 +16,8 @@ use Magento\Framework\DB\Adapter\DeadlockException; use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\Exception\CouldNotSaveException; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\StateException; use Magento\Framework\Exception\ValidatorException; /** @@ -116,11 +112,15 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $fileSystem; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageContentInterfaceFactory */ protected $contentFactory; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageProcessorInterface */ protected $imageProcessor; @@ -131,7 +131,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $extensionAttributesJoinProcessor; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor + * @var ProductRepository\MediaGalleryProcessor */ protected $mediaGalleryProcessor; @@ -378,53 +378,6 @@ private function assignProductToWebsites(\Magento\Catalog\Model\Product $product $product->setWebsiteIds($websiteIds); } - /** - * @param ProductInterface $product - * @param array $newEntry - * @return $this - * @throws InputException - * @throws StateException - * @throws \Magento\Framework\Exception\LocalizedException - */ - protected function processNewMediaGalleryEntry( - ProductInterface $product, - array $newEntry - ) { - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $newEntry['content']; - - /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */ - $mediaConfig = $product->getMediaConfig(); - $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); - - $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); - $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); - - if (!$product->hasGalleryAttribute()) { - throw new StateException(__('Requested product does not support images.')); - } - - $imageFileUri = $this->getMediaGalleryProcessor()->addImage( - $product, - $tmpFilePath, - isset($newEntry['types']) ? $newEntry['types'] : [], - true, - isset($newEntry['disabled']) ? $newEntry['disabled'] : true - ); - // Update additional fields that are still empty after addImage call - $this->getMediaGalleryProcessor()->updateImage( - $product, - $imageFileUri, - [ - 'label' => $newEntry['label'], - 'position' => $newEntry['position'], - 'disabled' => $newEntry['disabled'], - 'media_type' => $newEntry['media_type'], - ] - ); - return $this; - } - /** * Process product links, creating new links, updating and deleting existing links * @@ -483,67 +436,6 @@ private function processLinks(\Magento\Catalog\Api\Data\ProductInterface $produc return $this; } - /** - * Process Media gallery data before save product. - * - * Compare Media Gallery Entries Data with existing Media Gallery - * * If Media entry has not value_id set it as new - * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag - * * Merge Existing and new media gallery - * - * @param ProductInterface $product contains only existing media gallery items - * @param array $mediaGalleryEntries array which contains all media gallery items - * @return $this - * @throws InputException - * @throws StateException - * @throws LocalizedException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function processMediaGallery(ProductInterface $product, $mediaGalleryEntries) - { - $existingMediaGallery = $product->getMediaGallery('images'); - $newEntries = []; - $entriesById = []; - if (!empty($existingMediaGallery)) { - foreach ($mediaGalleryEntries as $entry) { - if (isset($entry['id'])) { - $entriesById[$entry['id']] = $entry; - } else { - $newEntries[] = $entry; - } - } - foreach ($existingMediaGallery as $key => &$existingEntry) { - if (isset($entriesById[$existingEntry['value_id']])) { - $updatedEntry = $entriesById[$existingEntry['value_id']]; - if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { - unset($updatedEntry['file']); - } - $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); - } else { - //set the removed flag - $existingEntry['removed'] = true; - } - } - unset($existingEntry); - $product->setData('media_gallery', ["images" => $existingMediaGallery]); - } else { - $newEntries = $mediaGalleryEntries; - } - - $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); - $images = $product->getMediaGallery('images'); - if ($images) { - foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { - $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); - } - } - } - $this->processEntries($product, $newEntries, $entriesById); - - return $this; - } - /** * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -580,7 +472,10 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO $this->processLinks($product, $productLinks); if (isset($productDataArray['media_gallery_entries'])) { - $this->processMediaGallery($product, $productDataArray['media_gallery_entries']); + $this->getMediaGalleryProcessor()->processMediaGallery( + $product, + $productDataArray['media_gallery_entries'] + ); } if (!$product->getOptionsReadonly()) { @@ -752,13 +647,13 @@ public function cleanCache() } /** - * @return Product\Gallery\Processor + * @return ProductRepository\MediaGalleryProcessor */ private function getMediaGalleryProcessor() { if (null === $this->mediaGalleryProcessor) { $this->mediaGalleryProcessor = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\Gallery\Processor::class); + ->get(ProductRepository\MediaGalleryProcessor::class); } return $this->mediaGalleryProcessor; } @@ -778,60 +673,4 @@ private function getCollectionProcessor() } return $this->collectionProcessor; } - - /** - * Convert extension attribute for product media gallery. - * - * @param array $newEntry - * @param array $extensionAttributes - * @return void - */ - private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) - { - foreach ($extensionAttributes as $code => $value) { - if (is_array($value)) { - $this->processExtensionAttributes($newEntry, $value); - } else { - $newEntry[$code] = $value; - } - } - unset($newEntry['extension_attributes']); - } - - /** - * Convert entries into product media gallery data and set to product. - * - * @param ProductInterface $product - * @param array $newEntries - * @param array $entriesById - * @throws InputException - * @throws LocalizedException - * @throws StateException - * @return void - */ - private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) - { - foreach ($newEntries as $newEntry) { - if (!isset($newEntry['content'])) { - throw new InputException(__('The image content is not valid.')); - } - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $this->contentFactory->create() - ->setName($newEntry['content'][ImageContentInterface::NAME]) - ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) - ->setType($newEntry['content'][ImageContentInterface::TYPE]); - $newEntry['content'] = $contentDataObject; - $this->processNewMediaGalleryEntry($product, $newEntry); - - $finalGallery = $product->getData('media_gallery'); - $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); - if (isset($newEntry['extension_attributes'])) { - $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); - } - $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); - $entriesById[$newEntryId] = $newEntry; - $finalGallery['images'][$newEntryId] = $newEntry; - $product->setData('media_gallery', $finalGallery); - } - } } diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php new file mode 100644 index 0000000000000..4cc31d98fdfc2 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -0,0 +1,218 @@ +processor = $processor; + $this->contentFactory = $contentFactory; + $this->imageProcessor = $imageProcessor; + } + + /** + * Process Media gallery data before save product. + * + * Compare Media Gallery Entries Data with existing Media Gallery + * * If Media entry has not value_id set it as new + * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag + * * Merge Existing and new media gallery + * + * @param ProductInterface $product contains only existing media gallery items. + * @param array $mediaGalleryEntries array which contains all media gallery items. + * @return void + * @throws InputException + * @throws StateException + * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) + { + $existingMediaGallery = $product->getMediaGallery('images'); + $newEntries = []; + $entriesById = []; + if (!empty($existingMediaGallery)) { + foreach ($mediaGalleryEntries as $entry) { + if (isset($entry['id'])) { + $entriesById[$entry['id']] = $entry; + } else { + $newEntries[] = $entry; + } + } + foreach ($existingMediaGallery as $key => &$existingEntry) { + if (isset($entriesById[$existingEntry['value_id']])) { + $updatedEntry = $entriesById[$existingEntry['value_id']]; + if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { + unset($updatedEntry['file']); + } + $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); + } else { + //set the removed flag. + $existingEntry['removed'] = true; + } + } + unset($existingEntry); + $product->setData('media_gallery', ["images" => $existingMediaGallery]); + } else { + $newEntries = $mediaGalleryEntries; + } + + $this->processor->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); + $images = $product->getMediaGallery('images'); + if ($images) { + foreach ($images as $image) { + if (!isset($image['removed']) && !empty($image['types'])) { + $this->processor->setMediaAttribute($product, $image['types'], $image['file']); + } + } + } + $this->processEntries($product, $newEntries, $entriesById); + } + + /** + * Convert entries into product media gallery data and set to product. + * + * @param ProductInterface $product + * @param array $newEntries + * @param array $entriesById + * @throws InputException + * @throws LocalizedException + * @throws StateException + * @return void + */ + private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) + { + foreach ($newEntries as $newEntry) { + if (!isset($newEntry['content'])) { + throw new InputException(__('The image content is not valid.')); + } + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $this->contentFactory->create() + ->setName($newEntry['content'][ImageContentInterface::NAME]) + ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) + ->setType($newEntry['content'][ImageContentInterface::TYPE]); + $newEntry['content'] = $contentDataObject; + $this->processNewMediaGalleryEntry($product, $newEntry); + + $finalGallery = $product->getData('media_gallery'); + $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + if (isset($newEntry['extension_attributes'])) { + $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); + } + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); + $entriesById[$newEntryId] = $newEntry; + $finalGallery['images'][$newEntryId] = $newEntry; + $product->setData('media_gallery', $finalGallery); + } + } + + /** + * Save gallery entry as image. + * + * @param ProductInterface $product + * @param array $newEntry + * @return void + * @throws InputException + * @throws StateException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function processNewMediaGalleryEntry( + ProductInterface $product, + array $newEntry + ) { + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $newEntry['content']; + + /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */ + $mediaConfig = $product->getMediaConfig(); + $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); + + $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); + $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); + + if (!$product->hasGalleryAttribute()) { + throw new StateException(__('Requested product does not support images.')); + } + + $imageFileUri = $this->processor->addImage( + $product, + $tmpFilePath, + isset($newEntry['types']) ? $newEntry['types'] : [], + true, + isset($newEntry['disabled']) ? $newEntry['disabled'] : true + ); + // Update additional fields that are still empty after addImage call. + $this->processor->updateImage( + $product, + $imageFileUri, + [ + 'label' => $newEntry['label'], + 'position' => $newEntry['position'], + 'disabled' => $newEntry['disabled'], + 'media_type' => $newEntry['media_type'], + ] + ); + } + + /** + * Convert extension attribute for product media gallery. + * + * @param array $newEntry + * @param array $extensionAttributes + * @return void + */ + private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) + { + foreach ($extensionAttributes as $code => $value) { + if (is_array($value)) { + $this->processExtensionAttributes($newEntry, $value); + } else { + $newEntry[$code] = $value; + } + } + unset($newEntry['extension_attributes']); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php new file mode 100644 index 0000000000000..02773b2fb3d70 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php @@ -0,0 +1,227 @@ +product = $this->createPartialMock( + \Magento\Catalog\Model\Product::class, + [ + 'hasGalleryAttribute', + 'getMediaConfig', + 'getMediaAttributes', + 'getMediaGalleryEntries', + ] + ); + $this->product->expects($this->any()) + ->method('hasGalleryAttribute') + ->willReturn(true); + $this->processor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contentFactory = $this->getMockBuilder(ImageContentInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->imageProcessor = $this->getMockBuilder(ImageProcessorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + MediaGalleryProcessor::class, + [ + 'processor' => $this->processor, + 'contentFactory' => $this->contentFactory, + 'imageProcessor' => $this->imageProcessor, + ] + ); + } + + /** + * Test add image. + * + * @return void + */ + public function testProcessWithNewMediaEntry() + { + $mediaGalleryEntries = [ + [ + 'value_id' => null, + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; + + //setup media attribute backend. + $mediaTmpPath = '/tmp'; + $absolutePath = '/a/b/filename.jpg'; + $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $mediaConfigMock->expects($this->once()) + ->method('getTmpMediaShortUrl') + ->with($absolutePath) + ->willReturn($mediaTmpPath . $absolutePath); + $this->product->setData('media_gallery', ['images' => $mediaGalleryEntries]); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'imageAttribute', 'small_image' => 'small_image_attribute']); + $this->product->expects($this->once()) + ->method('getMediaConfig') + ->willReturn($mediaConfigMock); + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + + //verify new entries. + $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->contentFactory->expects($this->once()) + ->method('create') + ->willReturn($contentDataObject); + + $this->imageProcessor->expects($this->once()) + ->method('processImageContent') + ->willReturn($absolutePath); + + $imageFileUri = 'imageFileUri'; + $this->processor->expects($this->once())->method('addImage') + ->with($this->product, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) + ->willReturn($imageFileUri); + $this->processor->expects($this->once())->method('updateImage') + ->with( + $this->product, + $imageFileUri, + [ + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'media_type' => 'media_type', + ] + ); + + $this->model->processMediaGallery($this->product, $mediaGalleryEntries); + } + + /** + * Test update(delete) images. + */ + public function testProcessExistingWithMediaGalleryEntries() + { + //update one entry, delete one entry. + $newEntries = [ + [ + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + ]; + + $existingMediaGallery = [ + 'images' => [ + [ + 'value_id' => 5, + 'label' => 'label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => true, + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + ], + ], + ]; + + $expectedResult = [ + [ + 'value_id' => 5, + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + 'removed' => true, + ], + ]; + + $this->product->setData('media_gallery', $existingMediaGallery); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'filename1', 'small_image' => 'filename2']); + + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + $this->processor->expects($this->once()) + ->method('setMediaAttribute') + ->with($this->product, ['image', 'small_image'], 'filename1'); + $this->model->processMediaGallery($this->product, $newEntries); + $this->assertEquals($expectedResult, $this->product->getMediaGallery('images')); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index a220b9a5768fe..14c84f4781a3a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -9,6 +9,7 @@ namespace Magento\Catalog\Test\Unit\Model; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\DB\Adapter\ConnectionException; @@ -139,7 +140,7 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase protected $storeManagerMock; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor|\PHPUnit_Framework_MockObject_MockObject + * @var MediaGalleryProcessor|\PHPUnit_Framework_MockObject_MockObject */ protected $mediaGalleryProcessor; @@ -234,7 +235,7 @@ protected function setUp() $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->mediaGalleryProcessor = $this->createMock(\Magento\Catalog\Model\Product\Gallery\Processor::class); + $this->mediaGalleryProcessor = $this->createMock(MediaGalleryProcessor::class); $this->collectionProcessorMock = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); @@ -1174,7 +1175,21 @@ public function testSaveExistingWithNewMediaGalleryEntries() ] ] ]; - + $expectedEntriesData = [ + [ + 'id' => null, + 'label' => "label_text", + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = [ @@ -1198,56 +1213,8 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $newEntriesData); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]); - - //setup media attribute backend - $mediaTmpPath = '/tmp'; - $absolutePath = '/a/b/filename.jpg'; - - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); - - $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) - ->disableOriginalConstructor() - ->getMock(); - $mediaConfigMock->expects($this->once()) - ->method('getTmpMediaShortUrl') - ->with($absolutePath) - ->willReturn($mediaTmpPath . $absolutePath); - $this->initializedProductMock->expects($this->once()) - ->method('getMediaConfig') - ->willReturn($mediaConfigMock); - - //verify new entries - $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) - ->disableOriginalConstructor() - ->setMethods(null) - ->getMock(); - $this->contentFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($contentDataObject); - - $this->imageProcessorMock->expects($this->once()) - ->method('processImageContent') - ->willReturn($absolutePath); - - $imageFileUri = "imageFileUri"; - $this->mediaGalleryProcessor->expects($this->once())->method('addImage') - ->with($this->initializedProductMock, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) - ->willReturn($imageFileUri); - $this->mediaGalleryProcessor->expects($this->once())->method('updateImage') - ->with( - $this->initializedProductMock, - $imageFileUri, - [ - 'label' => 'label_text', - 'position' => 10, - 'disabled' => false, - 'media_type' => 'media_type', - ] - ); + $this->mediaGalleryProcessor->expects($this->once())->method('processMediaGallery') + ->with($this->initializedProductMock, $expectedEntriesData); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); @@ -1325,24 +1292,6 @@ public function testSaveExistingWithMediaGalleryEntries() ], ], ]; - - $expectedResult = [ - [ - 'value_id' => 5, - 'id' => 5, - "label" => "new_label_text", - 'file' => 'filename1', - 'position' => 10, - 'disabled' => false, - 'types' => ['image', 'small_image'], - ], - [ - 'value_id' => 6, //will be deleted - 'file' => 'filename2', - 'removed' => true, - ], - ]; - $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = $newEntries; @@ -1352,21 +1301,15 @@ public function testSaveExistingWithMediaGalleryEntries() ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $existingMediaGallery); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "filename1", "small_image" => "filename2"]); - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); $this->mediaGalleryProcessor->expects($this->once()) - ->method('setMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image'], 'filename1'); + ->method('processMediaGallery') + ->with($this->initializedProductMock, $newEntries); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->any())->method('getMediaGalleryEntries')->willReturn(null); $this->model->save($this->productMock); - $this->assertEquals($expectedResult, $this->initializedProductMock->getMediaGallery('images')); } } From 93453a811e7cc5fb128fe7abd96233d47e010d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torben=20H=C3=B6hn?= Date: Wed, 10 Jan 2018 09:46:19 +0100 Subject: [PATCH 11/11] removed inline control structure --- app/code/Magento/Newsletter/Model/Subscriber.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 961ed539815e9..8f29798472f19 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -614,8 +614,9 @@ protected function _updateCustomerSubscription($customerId, $subscribe) $this->sendConfirmationSuccessEmail(); break; case self::STATUS_NOT_ACTIVE: - if ($isConfirmNeed) + if ($isConfirmNeed) { $this->sendConfirmationRequestEmail(); + } break; } } catch (MailException $e) {