diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Option.php b/app/code/Magento/Bundle/Model/ResourceModel/Option.php index 2ad7e57f522d664dd79c621ec188c354688a008f..46fd8b910f6f1e1b57d36ea4959361145b233bca 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Option.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Option.php @@ -81,31 +81,39 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { parent::_afterSave($object); - $condition = [ + $conditions = [ 'option_id = ?' => $object->getId(), 'store_id = ? OR store_id = 0' => $object->getStoreId(), 'parent_product_id = ?' => $object->getParentId() ]; $connection = $this->getConnection(); - $connection->delete($this->getTable('catalog_product_bundle_option_value'), $condition); - $data = new \Magento\Framework\DataObject(); - $data->setOptionId($object->getId()) - ->setStoreId($object->getStoreId()) - ->setParentProductId($object->getParentId()) - ->setTitle($object->getTitle()); - - $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); - - /** - * also saving default value if this store view scope - */ + if ($this->isOptionPresent($conditions)) { + $connection->update( + $this->getTable('catalog_product_bundle_option_value'), + [ + 'title' => $object->getTitle() + ], + $conditions + ); + } else { + $data = new \Magento\Framework\DataObject(); + $data->setOptionId($object->getId()) + ->setStoreId($object->getStoreId()) + ->setParentProductId($object->getParentId()) + ->setTitle($object->getTitle()); - if ($object->getStoreId()) { - $data->setStoreId(0); - $data->setTitle($object->getDefaultTitle()); $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + + /** + * also saving default value if this store view scope + */ + if ($object->getStoreId()) { + $data->setStoreId(0); + $data->setTitle($object->getDefaultTitle()); + $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + } } return $this; @@ -210,4 +218,26 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $this; } + + /** + * Is Bundle option present in the database + * + * @param array $conditions + * + * @return bool + */ + private function isOptionPresent($conditions) + { + $connection = $this->getConnection(); + + $select = $connection->select()->from($this->getTable('catalog_product_bundle_option_value')); + foreach ($conditions as $condition => $conditionValue) { + $select->where($condition, $conditionValue); + } + $select->limit(1); + + $rowSelect = $connection->fetchRow($select); + + return (is_array($rowSelect) && !empty($rowSelect)); + } } diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 83feea903f99343dae5f75dc01516945eae1e3d4..f260b01c02ef41d6b652c39750ed795c2f36e01f 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\ConnectionException; 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; @@ -329,6 +329,9 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa unset($productData['media_gallery']); if ($createNew) { $product = $this->productFactory->create(); + if (isset($productData['price']) && !isset($productData['product_type'])) { + $product->setTypeId(Product\Type::TYPE_SIMPLE); + } if ($this->storeManager->hasSingleStore()) { $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); } @@ -375,53 +378,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $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 * @@ -480,67 +436,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa 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) @@ -577,7 +472,10 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $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()) { @@ -749,13 +647,13 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa } /** - * @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; } @@ -775,60 +673,4 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa } 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 0000000000000000000000000000000000000000..4cc31d98fdfc2fd1d0d1ddc9487c0c5a1e301f6b --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -0,0 +1,218 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\ProductRepository; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Gallery\Processor; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ImageProcessorInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\StateException; + +/** + * Process Media gallery data for ProductRepository before save product. + */ +class MediaGalleryProcessor +{ + /** + * @var Processor + */ + private $processor; + + /** + * @var ImageContentInterfaceFactory + */ + private $contentFactory; + + /** + * @var ImageProcessorInterface + */ + private $imageProcessor; + + /** + * MediaGalleryProcessor constructor. + * + * @param Processor $processor + * @param ImageContentInterfaceFactory $contentFactory + * @param ImageProcessorInterface $imageProcessor + */ + public function __construct( + Processor $processor, + ImageContentInterfaceFactory $contentFactory, + ImageProcessorInterface $imageProcessor + ) { + $this->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 0000000000000000000000000000000000000000..02773b2fb3d703e507101ca3ad899c53fdacc84f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php @@ -0,0 +1,227 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\ProductRepository; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Gallery\Processor; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ImageProcessorInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Provide tests for ProductRepository/MediaGalleryProcessor. + */ +class MediaGalleryProcessorTest extends TestCase +{ + /** + * Test subject. + * + * @var MediaGalleryProcessor + */ + private $model; + + /** + * @var Processor|\PHPUnit_Framework_MockObject_MockObject + */ + private $processor; + + /** + * @var ImageContentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $contentFactory; + + /** + * @var ImageProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $imageProcessor; + + /** + * @var Product|\PHPUnit_Framework_MockObject_MockObject + */ + private $product; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->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 a220b9a5768fed1b0d043cb7d1629439e657fc7d..14c84f4781a3aa962dbbf04f2ec60d1e11954915 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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase $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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ] ] ]; - + $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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ->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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ], ], ]; - - $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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ->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')); } } diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 0a9f3c2d40dcad6495d77ef2269f20b72d48da4e..3197501e9b70b1f410f4765c2282276dfceed924 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -27,9 +27,6 @@ <event name="sales_model_service_quote_submit_failure"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\RevertQuoteInventoryObserver"/> </event> - <event name="restore_quote"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\RevertQuoteInventoryObserver"/> - </event> <event name="sales_order_item_cancel"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\CancelOrderItemObserver"/> </event> diff --git a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml index 201d6ffe4c68344608f274589dd647f69a336d9f..574cbe1107e883b110fff30fcc384b8415d0b205 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml +++ b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml @@ -35,8 +35,7 @@ <ol class="product-items <?= /* @noEscape */ $type ?>"> <?php $iterator = 1; ?> <?php foreach ($items as $_item): ?> - <?php if ($iterator++ != 1): ?></li><?php endif ?> - <li class="product-item"> + <?= /* @noEscape */ ($iterator++ == 1) ? '<li class="product-item">' : '</li><li class="product-item">' ?> <div class="product-item-info"> <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-photo"> <?= $block->getImage($_item, $image)->toHtml() ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index b224c96f07e9bb42c1c24a4bc8a752821e15b3ec..1d67b325e01c5d07e709144cdc0fec7745f94e1d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -24,7 +24,7 @@ <div class="field"> <label for="coupon_code" class="label"><span><?= /* @escapeNotVerified */ __('Enter discount code') ?></span></label> <div class="control"> - <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?= $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtml(__('Enter discount code')) ?>" /> + <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?= $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtml(__('Enter discount code')) ?>" <?php if (strlen($block->getCouponCode())): ?> disabled="disabled" <?php endif; ?> /> </div> </div> <div class="actions-toolbar"> diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index dceb5767edae91ce12db919370c0ea0a38c33d0d..42d7d91fb90e85785acdb3f6a53f1d37f6e680a6 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 @@ class OrderedProductAvailabilityChecker implements OrderedProductAvailabilityChe public function isAvailable(Item $item) { $buyRequest = $item->getBuyRequest(); - $superAttribute = $buyRequest->getData()['super_attribute']; + $superAttribute = $buyRequest->getData()['super_attribute'] ?? []; $connection = $this->getConnection(); $select = $connection->select(); $orderItemParentId = $item->getParentItem()->getProductId(); 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 324881cdc502821ea4251d43bc0ef24abce95d1c..cd6292b39e98921e649b3afc4bb3965063129725 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,10 +51,9 @@ define([ if (config.pageTrackingData.isAnonymizedIpActive) { ga('set', 'anonymizeIp', true); } - ga('send', 'pageview' + config.pageTrackingData.optPageUrl); // Process orders data - if (config.ordersTrackingData) { + if (config.ordersTrackingData.length) { ga('require', 'ec', 'ec.js'); //Set currency code @@ -75,6 +74,9 @@ define([ } ga('send', 'pageview'); + }else{ + // Process Data if not orders + ga('send', 'pageview' + config.pageTrackingData.optPageUrl); } } } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index b8d7ccf83af7ccc29a6e322e769cd39b267030d5..8f29798472f19c11178f4480c5272d2b4638b55d 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -604,14 +604,20 @@ class Subscriber extends \Magento\Framework\Model\AbstractModel $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 diff --git a/app/code/Magento/Quote/Model/Quote/Item.php b/app/code/Magento/Quote/Model/Quote/Item.php index d8177ddfe5236500cb5d2099bfab2b6d47ee9391..fe6d712500bcd7e6d61a782fe757dc78f67c96e5 100644 --- a/app/code/Magento/Quote/Model/Quote/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Item.php @@ -745,6 +745,9 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage unset($this->_options[$index]); unset($this->_optionsByCode[$option->getCode()]); } else { + if (!$option->getItem() || !$option->getItem()->getId()) { + $option->setItem($this); + } $option->save(); } } diff --git a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php index 3113721f8a59781406107f23a54f832b11fb976b..38bfcbf1d30ca445785faee62117cd4dfa57957f 100644 --- a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php +++ b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php @@ -19,22 +19,32 @@ class ValidationMessage /** * @var \Magento\Framework\Locale\CurrencyInterface + * @deprecated since 101.0.0 */ private $currency; + /** + * @var \Magento\Framework\Pricing\Helper\Data + */ + private $priceHelper; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\Locale\CurrencyInterface $currency + * @param \Magento\Framework\Pricing\Helper\Data $priceHelper */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Locale\CurrencyInterface $currency + \Magento\Framework\Locale\CurrencyInterface $currency, + \Magento\Framework\Pricing\Helper\Data $priceHelper = null ) { $this->scopeConfig = $scopeConfig; $this->storeManager = $storeManager; $this->currency = $currency; + $this->priceHelper = $priceHelper ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Pricing\Helper\Data::class); } /** @@ -50,13 +60,11 @@ class ValidationMessage \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); if (!$message) { - $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode(); - $minimumAmount = $this->currency->getCurrency($currencyCode)->toCurrency( - $this->scopeConfig->getValue( - 'sales/minimum_order/amount', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) - ); + $minimumAmount = $this->priceHelper->currency($this->scopeConfig->getValue( + 'sales/minimum_order/amount', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ), true, false); + $message = __('Minimum order amount is %1', $minimumAmount); } else { //Added in order to address the issue: https://github.com/magento/magento2/issues/8287 diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php index 64204ea1fb93dbeb35cf0b5f66bbd85632c787eb..272a4e3a4ba49206273330c8d864b262ea402bdf 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php @@ -26,19 +26,27 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject + * @deprecated since 101.0.0 */ private $currencyMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $priceHelperMock; + protected function setUp() { $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->currencyMock = $this->createMock(\Magento\Framework\Locale\CurrencyInterface::class); + $this->priceHelperMock = $this->createMock(\Magento\Framework\Pricing\Helper\Data::class); $this->model = new \Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage( $this->scopeConfigMock, $this->storeManagerMock, - $this->currencyMock + $this->currencyMock, + $this->priceHelperMock ); } @@ -46,8 +54,6 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase { $minimumAmount = 20; $minimumAmountCurrency = '$20'; - $currencyCode = 'currency_code'; - $this->scopeConfigMock->expects($this->at(0)) ->method('getValue') ->with('sales/minimum_order/description', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) @@ -58,27 +64,13 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase ->with('sales/minimum_order/amount', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) ->willReturn($minimumAmount); - $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getCurrentCurrencyCode']); - $storeMock->expects($this->once())->method('getCurrentCurrencyCode')->willReturn($currencyCode); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $this->priceHelperMock->expects($this->once()) + ->method('currency') + ->with($minimumAmount, true, false) + ->will($this->returnValue($minimumAmountCurrency)); - $currencyMock = $this->createMock(\Magento\Framework\Currency::class); - $this->currencyMock->expects($this->once()) - ->method('getCurrency') - ->with($currencyCode) - ->willReturn($currencyMock); - - $currencyMock->expects($this->once()) - ->method('toCurrency') - ->with($minimumAmount) - ->willReturn($minimumAmountCurrency); - - $this->assertEquals( - __('Minimum order amount is %1', $minimumAmountCurrency), - $this->model->getMessage() - ); + $this->assertEquals(__('Minimum order amount is %1', $minimumAmountCurrency), $this->model->getMessage()); } - public function testGetConfigMessage() { $configMessage = 'config_message'; diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index 7246460382fa75e371257ee8d0646cf007d17f82..d622b5ea5762d7fa796f3a8999c0e16b844c461d 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -27,7 +27,7 @@ id="discount-code" name="discount_code" data-validate="{'required-entry':true}" - data-bind="value: couponCode, attr:{placeholder: $t('Enter discount code')} " /> + data-bind="value: couponCode, attr:{disabled:isApplied() , placeholder: $t('Enter discount code')} " /> </div> </div> </div> diff --git a/app/code/Magento/Vault/Setup/InstallSchema.php b/app/code/Magento/Vault/Setup/InstallSchema.php index 32e1d62754439f5324c474f848517d48fce84b10..dd65f7a8c9772883318ce2f91d559c06a68f94f2 100644 --- a/app/code/Magento/Vault/Setup/InstallSchema.php +++ b/app/code/Magento/Vault/Setup/InstallSchema.php @@ -90,13 +90,13 @@ class InstallSchema implements InstallSchemaInterface 'is_active', Table::TYPE_BOOLEAN, null, - ['nullable' => false, 'dafault' => true], + ['nullable' => false, 'default' => true], 'Is active flag' )->addColumn( 'is_visible', Table::TYPE_BOOLEAN, null, - ['nullable' => false, 'dafault' => true], + ['nullable' => false, 'default' => true], 'Is visible flag' )->addIndex( $setup->getIdxName( diff --git a/app/code/Magento/Vault/Setup/UpgradeSchema.php b/app/code/Magento/Vault/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..643bd1751f6689a7b41ba4caffea7c4f11318bab --- /dev/null +++ b/app/code/Magento/Vault/Setup/UpgradeSchema.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Vault\Setup; + +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\DB\Ddl\Table; + +/** + * Upgrade the Vault module DB scheme + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * @inheritdoc + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.0.3', '<')) { + $this->upgradeTokenTableDefaultValues($setup); + } + $setup->endSetup(); + } + + /** + * @param SchemaSetupInterface $setup + * @return void + */ + private function upgradeTokenTableDefaultValues(SchemaSetupInterface $setup) + { + $columns = ['is_active', 'is_visible']; + + foreach ($columns as $columnName) { + $setup->getConnection()->modifyColumn( + $setup->getTable(InstallSchema::PAYMENT_TOKEN_TABLE), + $columnName, + [ + 'type' => Table::TYPE_BOOLEAN, + 'nullable' => false, + 'default' => '1' + ] + ); + } + } +} diff --git a/app/code/Magento/Vault/etc/module.xml b/app/code/Magento/Vault/etc/module.xml index 1a7d1fe7d09fd17c7d6109c81f83b7ac83e1fbf2..253e7f13aaadc4171ae26e4a877c231d69bac9d3 100644 --- a/app/code/Magento/Vault/etc/module.xml +++ b/app/code/Magento/Vault/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Vault" setup_version="2.0.2"> + <module name="Magento_Vault" setup_version="2.0.3"> <sequence> <module name="Magento_Sales"/> <module name="Magento_Store"/> 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 09f6362c833d4de773bc6702fc0f5a28c614b473..9dab97621f2f54de8da46ab592a3f79fb765b2d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -326,6 +326,32 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract } } + /** + * 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 9518e9c0cdf4fbf476e2cf554ce566afc3547714..330487b757f617bec675e0a87ddba0a9c99049e1 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 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase $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()); + } } diff --git a/lib/web/mage/apply/main.js b/lib/web/mage/apply/main.js index 60b737e59e11089fd269f963720f8954ce3275ed..489e467f9b1100aa128d33fefe95324a75b7f36b 100644 --- a/lib/web/mage/apply/main.js +++ b/lib/web/mage/apply/main.js @@ -32,6 +32,12 @@ define([ } else if ($(el)[component]) { $(el)[component](config); } + }, function (error) { + if ('console' in window && typeof window.console.error === 'function') { + console.error(error); + } + + return true; }); } diff --git a/lib/web/magnifier/magnifier.js b/lib/web/magnifier/magnifier.js index 958af0e96641bed61c90bb161170f4ffa935b26e..c47536436892235cb90eb7315d360970857ab080 100644 --- a/lib/web/magnifier/magnifier.js +++ b/lib/web/magnifier/magnifier.js @@ -588,7 +588,7 @@ _init($box, gOptions); }); - $(document).on('mousemove', onMousemove); + $box.on('mousemove', onMousemove); _init($box, gOptions); }