diff --git a/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php b/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php index 49f82562f33db19b378d765e063ea465c800db30..09cd6793b4785801884150c7c88800fcdf43de59 100644 --- a/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php +++ b/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php @@ -5,6 +5,9 @@ */ namespace Magento\Catalog\Console\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command { /** @@ -58,10 +61,8 @@ class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command /** * {@inheritdoc} */ - protected function execute( - \Symfony\Component\Console\Input\InputInterface $input, - \Symfony\Component\Console\Output\OutputInterface $output - ) { + protected function execute(InputInterface $input, OutputInterface $output) + { $this->appState->setAreaCode(\Magento\Framework\App\Area::AREA_GLOBAL); /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ @@ -73,6 +74,7 @@ class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } + $errorMessage = ''; try { foreach ($productIds as $productId) { try { @@ -82,9 +84,13 @@ class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command continue; } - /** @var \Magento\Catalog\Model\Product\Image\Cache $imageCache */ - $imageCache = $this->imageCacheFactory->create(); - $imageCache->generate($product); + try { + /** @var \Magento\Catalog\Model\Product\Image\Cache $imageCache */ + $imageCache = $this->imageCacheFactory->create(); + $imageCache->generate($product); + } catch (\Magento\Framework\Exception\RuntimeException $e) { + $errorMessage = $e->getMessage(); + } $output->write("."); } @@ -95,6 +101,12 @@ class ImagesResizeCommand extends \Symfony\Component\Console\Command\Command } $output->write("\n"); - $output->writeln("<info>Product images resized successfully</info>"); + $output->writeln("<info>Product images resized successfully.</info>"); + + if ($errorMessage !== '') { + $output->writeln("<comment>{$errorMessage}</comment>"); + } + + return 0; } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php index 270a2f229678bc06eee7a5fd22ede2ab0ad38a5b..c19efac12ae0e4350b44d8908f22600fb00ebd1d 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php @@ -118,6 +118,9 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter $attribute->setAttributeId($existingModel->getAttributeId()); $attribute->setIsUserDefined($existingModel->getIsUserDefined()); $attribute->setFrontendInput($existingModel->getFrontendInput()); + if ($attribute->getIsUserDefined()) { + $this->processAttributeData($attribute); + } if (is_array($attribute->getFrontendLabels())) { $defaultFrontendLabel = $attribute->getDefaultFrontendLabel(); @@ -156,15 +159,7 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter $this->validateCode($attribute->getAttributeCode()); $this->validateFrontendInput($attribute->getFrontendInput()); - $attribute->setBackendType( - $attribute->getBackendTypeByInput($attribute->getFrontendInput()) - ); - $attribute->setSourceModel( - $this->productHelper->getAttributeSourceModelByInputType($attribute->getFrontendInput()) - ); - $attribute->setBackendModel( - $this->productHelper->getAttributeBackendModelByInputType($attribute->getFrontendInput()) - ); + $this->processAttributeData($attribute); $attribute->setEntityTypeId( $this->eavConfig ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE) @@ -275,4 +270,23 @@ class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInter throw InputException::invalidFieldValue('frontend_input', $frontendInput); } } + + /** + * Process attribute data based on attribute frontend input type. + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @return void + */ + private function processAttributeData(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute) + { + $attribute->setBackendType( + $attribute->getBackendTypeByInput($attribute->getFrontendInput()) + ); + $attribute->setSourceModel( + $this->productHelper->getAttributeSourceModelByInputType($attribute->getFrontendInput()) + ); + $attribute->setBackendModel( + $this->productHelper->getAttributeBackendModelByInputType($attribute->getFrontendInput()) + ); + } } diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php index e94104ae473a0a67760c299904039e47e7cba5d2..a7941ba5c0a792234b01cbba701da5231c1f263b 100644 --- a/app/code/Magento/Catalog/Model/Product/Copier.php +++ b/app/code/Magento/Catalog/Model/Product/Copier.php @@ -1,18 +1,24 @@ <?php /** - * Catalog product copier. Creates product duplicate - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model\Product; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\Product\Option\Repository as OptionRepository; +/** + * Catalog product copier. Creates product duplicate + */ class Copier { /** - * @var Option\Repository + * @var OptionRepository */ protected $optionRepository; @@ -22,22 +28,22 @@ class Copier protected $copyConstructor; /** - * @var \Magento\Catalog\Model\ProductFactory + * @var ProductFactory */ protected $productFactory; /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ protected $metadataPool; /** * @param CopyConstructorInterface $copyConstructor - * @param \Magento\Catalog\Model\ProductFactory $productFactory + * @param ProductFactory $productFactory */ public function __construct( CopyConstructorInterface $copyConstructor, - \Magento\Catalog\Model\ProductFactory $productFactory + ProductFactory $productFactory ) { $this->productFactory = $productFactory; $this->copyConstructor = $copyConstructor; @@ -46,18 +52,16 @@ class Copier /** * Create product duplicate * - * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Catalog\Model\Product + * @param Product $product + * @return Product */ - public function copy(\Magento\Catalog\Model\Product $product) + public function copy(Product $product) { $product->getWebsiteIds(); $product->getCategoryIds(); - /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */ $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); - /** @var \Magento\Catalog\Model\Product $duplicate */ $duplicate = $this->productFactory->create(); $productData = $product->getData(); $productData = $this->removeStockItem($productData); @@ -83,6 +87,7 @@ class Copier $duplicate->save(); $isDuplicateSaved = true; } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { + } catch (\Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException $e) { } } while (!$isDuplicateSaved); $this->getOptionRepository()->duplicate($product, $duplicate); @@ -94,27 +99,25 @@ class Copier } /** - * @return Option\Repository + * @return OptionRepository * @deprecated 101.0.0 */ private function getOptionRepository() { if (null === $this->optionRepository) { - $this->optionRepository = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\Option\Repository::class); + $this->optionRepository = ObjectManager::getInstance()->get(OptionRepository::class); } return $this->optionRepository; } /** - * @return \Magento\Framework\EntityManager\MetadataPool + * @return MetadataPool * @deprecated 101.0.0 */ private function getMetadataPool() { if (null === $this->metadataPool) { - $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\MetadataPool::class); + $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); } return $this->metadataPool; } diff --git a/app/code/Magento/Catalog/Model/Product/Image/Cache.php b/app/code/Magento/Catalog/Model/Product/Image/Cache.php index c5e5e0ecac4c05d37fc31b319533747b997ac182..cd66257657cb17a39c2cff58e415407a5abe0bde 100644 --- a/app/code/Magento/Catalog/Model/Product/Image/Cache.php +++ b/app/code/Magento/Catalog/Model/Product/Image/Cache.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Product; use Magento\Theme\Model\ResourceModel\Theme\Collection as ThemeCollection; use Magento\Framework\App\Area; use Magento\Framework\View\ConfigInterface; +use Psr\Log\LoggerInterface; class Cache { @@ -33,19 +34,29 @@ class Cache */ protected $data = []; + /** + * Logger. + * + * @var LoggerInterface + */ + private $logger; + /** * @param ConfigInterface $viewConfig * @param ThemeCollection $themeCollection * @param ImageHelper $imageHelper + * @param LoggerInterface $logger */ public function __construct( ConfigInterface $viewConfig, ThemeCollection $themeCollection, - ImageHelper $imageHelper + ImageHelper $imageHelper, + LoggerInterface $logger = null ) { $this->viewConfig = $viewConfig; $this->themeCollection = $themeCollection; $this->imageHelper = $imageHelper; + $this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -74,21 +85,37 @@ class Cache } /** - * Resize product images and save results to image cache + * Resize product images and save results to image cache. * * @param Product $product + * * @return $this + * @throws \Exception */ public function generate(Product $product) { + $isException = false; $galleryImages = $product->getMediaGalleryImages(); if ($galleryImages) { foreach ($galleryImages as $image) { foreach ($this->getData() as $imageData) { - $this->processImageData($product, $imageData, $image->getFile()); + try { + $this->processImageData($product, $imageData, $image->getFile()); + } catch (\Exception $e) { + $isException = true; + $this->logger->error($e->getMessage()); + $this->logger->error(__('The image could not be resized: ') . $image->getPath()); + } } } } + + if ($isException === true) { + throw new \Magento\Framework\Exception\RuntimeException( + __('Some images could not be resized. See log file for details.') + ); + } + return $this; } diff --git a/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php b/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php index 457ba7b94529b6e4ab4ddf4b1fd96365422d7333..b4687e18daf8d3f607e1b35d7db0c62926c5c43e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php @@ -208,4 +208,44 @@ class ImagesResizeCommandTest extends \PHPUnit\Framework\TestCase ->method('create') ->willReturn($this->imageCache); } + + public function testExecuteWithExceptionInGenerate() + { + $productsIds = [1, 2]; + + $this->appState->expects($this->once()) + ->method('setAreaCode') + ->with(Area::AREA_GLOBAL) + ->willReturnSelf(); + + $this->productCollection->expects($this->once()) + ->method('getAllIds') + ->willReturn($productsIds); + + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->productRepository->expects($this->at(0)) + ->method('getById') + ->with($productsIds[0]) + ->willReturn($productMock); + $this->productRepository->expects($this->at(1)) + ->method('getById') + ->with($productsIds[1]) + ->willThrowException(new NoSuchEntityException()); + + $this->imageCache->expects($this->exactly(count($productsIds) - 1)) + ->method('generate') + ->with($productMock) + ->willThrowException(new \Magento\Framework\Exception\RuntimeException(__('Some text is here.'))); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([]); + + $this->assertContains( + 'Some text is here.', + $commandTester->getDisplay() + ); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php index 3ff3601da8ccc7810fd67f8a8d70ee7ed437bf70..428ef432888f02ddd4edda3111cba69cb5db6a89 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/CacheTest.php @@ -189,6 +189,58 @@ class CacheTest extends \PHPUnit\Framework\TestCase $this->model->generate($this->product); } + /** + * @expectedException \Magento\Framework\Exception\RuntimeException + */ + public function testGenerateWithException() + { + $imageFile = 'image.jpg'; + $imageItem = $this->objectManager->getObject( + \Magento\Framework\DataObject::class, + [ + 'data' => ['file' => $imageFile] + ] + ); + $this->mediaGalleryCollection->expects($this->once()) + ->method('getIterator') + ->willReturn(new \ArrayIterator([$imageItem])); + + $this->product->expects($this->atLeastOnce()) + ->method('getMediaGalleryImages') + ->willReturn($this->mediaGalleryCollection); + + $data = $this->getTestData(); + $this->config->expects($this->once()) + ->method('getMediaEntities') + ->with('Magento_Catalog') + ->willReturn($data); + + $themeMock = $this->getMockBuilder(\Magento\Theme\Model\Theme::class) + ->disableOriginalConstructor() + ->getMock(); + $themeMock->expects($this->exactly(3)) + ->method('getCode') + ->willReturn('Magento\theme'); + + $this->themeCollection->expects($this->once()) + ->method('loadRegisteredThemes') + ->willReturn([$themeMock]); + + $this->viewConfig->expects($this->once()) + ->method('getViewConfig') + ->with([ + 'area' => Area::AREA_FRONTEND, + 'themeModel' => $themeMock, + ]) + ->willReturn($this->config); + + $this->imageHelper->expects($this->exactly(3)) + ->method('init') + ->willThrowException(new \Exception('Some text ')); + + $this->model->generate($this->product); + } + /** * @return array */ diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php index a2242ff0f355b3bca9d332ca5b59ec052cd61cb7..5887c76e8ddc23ef1ce51e822b46f57400d513f6 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php @@ -3,10 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation; use Magento\Catalog\Model\Product; -use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder; use Magento\Customer\Model\Session; use Magento\Eav\Model\Config; use Magento\Framework\App\ResourceConnection; @@ -19,7 +20,7 @@ use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\App\ObjectManager; /** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * DataProvider for Catalog search Mysql. */ class DataProvider implements DataProviderInterface { @@ -48,23 +49,31 @@ class DataProvider implements DataProviderInterface */ private $connection; + /** + * @var QueryBuilder; + */ + private $queryBuilder; + /** * @param Config $eavConfig * @param ResourceConnection $resource * @param ScopeResolverInterface $scopeResolver * @param Session $customerSession + * @param QueryBuilder|null $queryBuilder */ public function __construct( Config $eavConfig, ResourceConnection $resource, ScopeResolverInterface $scopeResolver, - Session $customerSession + Session $customerSession, + QueryBuilder $queryBuilder = null ) { $this->eavConfig = $eavConfig; $this->resource = $resource; $this->connection = $resource->getConnection(); $this->scopeResolver = $scopeResolver; $this->customerSession = $customerSession; + $this->queryBuilder = $queryBuilder ?: ObjectManager::getInstance()->get(QueryBuilder::class); } /** @@ -79,47 +88,13 @@ class DataProvider implements DataProviderInterface $attribute = $this->eavConfig->getAttribute(Product::ENTITY, $bucket->getField()); - $select = $this->getSelect(); - - $select->joinInner( - ['entities' => $entityIdsTable->getName()], - 'main_table.entity_id = entities.entity_id', - [] + $select = $this->queryBuilder->build( + $attribute, + $entityIdsTable->getName(), + $currentScope, + $this->customerSession->getCustomerGroupId() ); - if ($attribute->getAttributeCode() === 'price') { - /** @var \Magento\Store\Model\Store $store */ - $store = $this->scopeResolver->getScope($currentScope); - if (!$store instanceof \Magento\Store\Model\Store) { - throw new \RuntimeException('Illegal scope resolved'); - } - $table = $this->resource->getTableName('catalog_product_index_price'); - $select->from(['main_table' => $table], null) - ->columns([BucketInterface::FIELD_VALUE => 'main_table.min_price']) - ->where('main_table.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) - ->where('main_table.website_id = ?', $store->getWebsiteId()); - } else { - $currentScopeId = $this->scopeResolver->getScope($currentScope) - ->getId(); - $table = $this->resource->getTableName( - 'catalog_product_index_eav' . ($attribute->getBackendType() === 'decimal' ? '_decimal' : '') - ); - $subSelect = $select; - $subSelect->from(['main_table' => $table], ['main_table.entity_id', 'main_table.value']) - ->distinct() - ->joinLeft( - ['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')], - 'main_table.source_id = stock_index.product_id', - [] - ) - ->where('main_table.attribute_id = ?', $attribute->getAttributeId()) - ->where('main_table.store_id = ? ', $currentScopeId) - ->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK); - $parentSelect = $this->getSelect(); - $parentSelect->from(['main_table' => $subSelect], ['main_table.value']); - $select = $parentSelect; - } - return $select; } @@ -130,12 +105,4 @@ class DataProvider implements DataProviderInterface { return $this->connection->fetchAssoc($select); } - - /** - * @return Select - */ - private function getSelect() - { - return $this->connection->select(); - } } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..ca077ef7227d5822832005c57fe1ac7451c37e7c --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider; + +use Magento\CatalogInventory\Model\Configuration as CatalogInventoryConfiguration; +use Magento\CatalogInventory\Model\Stock; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\Search\Request\BucketInterface; + +/** + * Attribute query builder + */ +class QueryBuilder +{ + /** + * @var Resource + */ + private $resource; + + /** + * @var ScopeResolverInterface + */ + private $scopeResolver; + + /** + * @var CatalogInventoryConfiguration + */ + private $inventoryConfig; + + /** + * @param ResourceConnection $resource + * @param ScopeResolverInterface $scopeResolver + * @param CatalogInventoryConfiguration $inventoryConfig + */ + public function __construct( + ResourceConnection $resource, + ScopeResolverInterface $scopeResolver, + CatalogInventoryConfiguration $inventoryConfig + ) { + $this->resource = $resource; + $this->scopeResolver = $scopeResolver; + $this->inventoryConfig = $inventoryConfig; + } + + /** + * Build select. + * + * @param AbstractAttribute $attribute + * @param string $tableName + * @param int $currentScope + * @param int $customerGroupId + * + * @return Select + */ + public function build( + AbstractAttribute $attribute, + string $tableName, + int $currentScope, + int $customerGroupId + ) : Select { + $select = $this->resource->getConnection()->select(); + $select->joinInner( + ['entities' => $tableName], + 'main_table.entity_id = entities.entity_id', + [] + ); + + if ($attribute->getAttributeCode() === 'price') { + return $this->buildQueryForPriceAttribute($currentScope, $customerGroupId, $select); + } + + return $this->buildQueryForAttribute($currentScope, $attribute, $select); + } + + /** + * Build select for price attribute. + * + * @param int $currentScope + * @param int $customerGroupId + * @param Select $select + * + * @return Select + */ + private function buildQueryForPriceAttribute( + int $currentScope, + int $customerGroupId, + Select $select + ) : Select { + /** @var \Magento\Store\Model\Store $store */ + $store = $this->scopeResolver->getScope($currentScope); + if (!$store instanceof \Magento\Store\Model\Store) { + throw new \RuntimeException('Illegal scope resolved'); + } + + $table = $this->resource->getTableName('catalog_product_index_price'); + $select->from(['main_table' => $table], null) + ->columns([BucketInterface::FIELD_VALUE => 'main_table.min_price']) + ->where('main_table.customer_group_id = ?', $customerGroupId) + ->where('main_table.website_id = ?', $store->getWebsiteId()); + + return $select; + } + + /** + * Build select for attribute. + * + * @param int $currentScope + * @param AbstractAttribute $attribute + * @param Select $select + * + * @return Select + */ + private function buildQueryForAttribute( + int $currentScope, + AbstractAttribute $attribute, + Select $select + ) : Select { + $currentScopeId = $this->scopeResolver->getScope($currentScope)->getId(); + $table = $this->resource->getTableName( + 'catalog_product_index_eav' . ($attribute->getBackendType() === 'decimal' ? '_decimal' : '') + ); + $select->from(['main_table' => $table], ['main_table.entity_id', 'main_table.value']) + ->distinct() + ->joinLeft( + ['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')], + 'main_table.source_id = stock_index.product_id', + [] + ) + ->where('main_table.attribute_id = ?', $attribute->getAttributeId()) + ->where('main_table.store_id = ? ', $currentScopeId); + + if (!$this->inventoryConfig->isShowOutOfStock($currentScopeId)) { + $select->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK); + } + + $parentSelect = $this->resource->getConnection()->select(); + $parentSelect->from(['main_table' => $select], ['main_table.value']); + return $parentSelect; + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b52664df749fe0954875a7241dc80636aae45b1f --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php @@ -0,0 +1,154 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Aggregation\DataProvider; + +use Magento\CatalogInventory\Model\Configuration as CatalogInventoryConfiguration; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Store\Model\Store; + +/** + * Test for Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder. + */ +class QueryBuilderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var QueryBuilder + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $scopeResolverMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $adapterMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $inventoryConfigMock; + + protected function setUp() + { + $this->resourceConnectionMock = $this->createMock(ResourceConnection::class); + $this->scopeResolverMock = $this->createMock(ScopeResolverInterface::class); + $this->adapterMock = $this->createMock(AdapterInterface::class); + $this->inventoryConfigMock = $this->createMock(CatalogInventoryConfiguration::class); + + $this->resourceConnectionMock->expects($this->atLeastOnce()) + ->method('getConnection') + ->willReturn($this->adapterMock); + + $this->model = new QueryBuilder( + $this->resourceConnectionMock, + $this->scopeResolverMock, + $this->inventoryConfigMock + ); + } + + public function testBuildWithPriceAttributeCode() + { + $tableName = 'test_table'; + $scope = 1; + $selectMock = $this->createMock(Select::class); + $attributeMock = $this->createMock(AbstractAttribute::class); + $storeMock = $this->createMock(Store::class); + + $this->adapterMock->expects($this->atLeastOnce())->method('select') + ->willReturn($selectMock); + $selectMock->expects($this->once())->method('joinInner') + ->with(['entities' => $tableName], 'main_table.entity_id = entities.entity_id', []); + $attributeMock->expects($this->once())->method('getAttributeCode') + ->willReturn('price'); + $this->scopeResolverMock->expects($this->once())->method('getScope') + ->with($scope)->willReturn($storeMock); + $storeMock->expects($this->once())->method('getWebsiteId')->willReturn(1); + $this->resourceConnectionMock->expects($this->once())->method('getTableName') + ->with('catalog_product_index_price')->willReturn('catalog_product_index_price'); + $selectMock->expects($this->once())->method('from') + ->with(['main_table' => 'catalog_product_index_price'], null) + ->willReturn($selectMock); + $selectMock->expects($this->once())->method('columns') + ->with(['value' => 'main_table.min_price']) + ->willReturn($selectMock); + $selectMock->expects($this->exactly(2))->method('where') + ->withConsecutive( + ['main_table.customer_group_id = ?', 1], + ['main_table.website_id = ?', 1] + )->willReturn($selectMock); + + $this->model->build($attributeMock, $tableName, $scope, 1); + } + + public function testBuildWithNotPriceAttributeCode() + { + $tableName = 'test_table'; + $scope = 1; + $selectMock = $this->createMock(Select::class); + $attributeMock = $this->createMock(AbstractAttribute::class); + $storeMock = $this->createMock(Store::class); + + $this->adapterMock->expects($this->atLeastOnce())->method('select') + ->willReturn($selectMock); + $selectMock->expects($this->once())->method('joinInner') + ->with(['entities' => $tableName], 'main_table.entity_id = entities.entity_id', []); + $attributeMock->expects($this->once())->method('getBackendType') + ->willReturn('decimal'); + $this->scopeResolverMock->expects($this->once())->method('getScope') + ->with($scope)->willReturn($storeMock); + $storeMock->expects($this->once())->method('getId')->willReturn(1); + $this->resourceConnectionMock->expects($this->exactly(2))->method('getTableName') + ->withConsecutive( + ['catalog_product_index_eav_decimal'], + ['cataloginventory_stock_status'] + )->willReturnOnConsecutiveCalls( + 'catalog_product_index_eav_decimal', + 'cataloginventory_stock_status' + ); + + $selectMock->expects($this->exactly(2))->method('from') + ->withConsecutive( + [ + ['main_table' => 'catalog_product_index_eav_decimal'], + ['main_table.entity_id', 'main_table.value'] + ], + [['main_table' => $selectMock], ['main_table.value']] + ) + ->willReturn($selectMock); + $selectMock->expects($this->once())->method('distinct')->willReturn($selectMock); + $selectMock->expects($this->once())->method('joinLeft') + ->with( + ['stock_index' => 'cataloginventory_stock_status'], + 'main_table.source_id = stock_index.product_id', + [] + )->willReturn($selectMock); + $attributeMock->expects($this->once())->method('getAttributeId')->willReturn(3); + $selectMock->expects($this->exactly(3))->method('where') + ->withConsecutive( + ['main_table.attribute_id = ?', 3], + ['main_table.store_id = ? ', 1], + ['stock_index.stock_status = ?', Stock::STOCK_IN_STOCK] + )->willReturn($selectMock); + $this->inventoryConfigMock->expects($this->once())->method('isShowOutOfStock')->with(1)->willReturn(false); + + $this->model->build($attributeMock, $tableName, $scope, 1); + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php index 4305bc5cb0706c69ba4201e23360b75a80a08afd..7c558f60b7433b90b35060254cd94b5ffc0a38ac 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php @@ -7,6 +7,7 @@ namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Aggregation; use Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider; +use Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder; use Magento\Eav\Model\Config; use Magento\Customer\Model\Session; use Magento\Framework\App\ResourceConnection; @@ -21,6 +22,8 @@ use Magento\Catalog\Model\Product; use Magento\Framework\DB\Ddl\Table; /** + * Test for Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DataProviderTest extends \PHPUnit\Framework\TestCase @@ -55,6 +58,11 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase */ private $adapterMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $queryBuilderMock; + protected function setUp() { $this->eavConfigMock = $this->createMock(Config::class); @@ -63,72 +71,53 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase $this->sessionMock = $this->createMock(Session::class); $this->adapterMock = $this->createMock(AdapterInterface::class); $this->resourceConnectionMock->expects($this->once())->method('getConnection')->willReturn($this->adapterMock); + $this->queryBuilderMock = $this->createMock(QueryBuilder::class); $this->model = new DataProvider( $this->eavConfigMock, $this->resourceConnectionMock, $this->scopeResolverMock, - $this->sessionMock + $this->sessionMock, + $this->queryBuilderMock ); } - public function testGetDataSetUsesFrontendPriceIndexerTableIfAttributeIsPrice() + public function testGetDataSet() { $storeId = 1; - $attributeCode = 'price'; + $attributeCode = 'my_decimal'; $scopeMock = $this->createMock(Store::class); $scopeMock->expects($this->any())->method('getId')->willReturn($storeId); + $dimensionMock = $this->createMock(Dimension::class); $dimensionMock->expects($this->any())->method('getValue')->willReturn($storeId); + $this->scopeResolverMock->expects($this->any())->method('getScope')->with($storeId)->willReturn($scopeMock); $bucketMock = $this->createMock(BucketInterface::class); $bucketMock->expects($this->once())->method('getField')->willReturn($attributeCode); + $attributeMock = $this->createMock(Attribute::class); - $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); - $this->eavConfigMock->expects($this->once()) - ->method('getAttribute')->with(Product::ENTITY, $attributeCode) - ->willReturn($attributeMock); + $this->eavConfigMock->expects($this->once())->method('getAttribute') + ->with(Product::ENTITY, $attributeCode)->willReturn($attributeMock); - $selectMock = $this->createMock(Select::class); - $selectMock->expects($this->any())->method('from')->willReturnSelf(); - $selectMock->expects($this->any())->method('where')->willReturnSelf(); - $selectMock->expects($this->any())->method('columns')->willReturnSelf(); - $this->adapterMock->expects($this->once())->method('select')->willReturn($selectMock); $tableMock = $this->createMock(Table::class); + $tableMock->expects($this->once())->method('getName')->willReturn('test'); + + $this->sessionMock->expects($this->once())->method('getCustomerGroupId')->willReturn(1); + + $this->queryBuilderMock->expects($this->once())->method('build') + ->with($attributeMock, 'test', $storeId, 1); $this->model->getDataSet($bucketMock, ['scope' => $dimensionMock], $tableMock); } - public function testGetDataSetUsesFrontendPriceIndexerTableForDecimalAttributes() + public function testExecute() { - $storeId = 1; - $attributeCode = 'my_decimal'; - - $scopeMock = $this->createMock(Store::class); - $scopeMock->expects($this->any())->method('getId')->willReturn($storeId); - $dimensionMock = $this->createMock(Dimension::class); - $dimensionMock->expects($this->any())->method('getValue')->willReturn($storeId); - $this->scopeResolverMock->expects($this->any())->method('getScope')->with($storeId)->willReturn($scopeMock); - - $bucketMock = $this->createMock(BucketInterface::class); - $bucketMock->expects($this->once())->method('getField')->willReturn($attributeCode); - $attributeMock = $this->createMock(Attribute::class); - $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); - $this->eavConfigMock->expects($this->once()) - ->method('getAttribute')->with(Product::ENTITY, $attributeCode) - ->willReturn($attributeMock); - $selectMock = $this->createMock(Select::class); - $selectMock->expects($this->any())->method('from')->willReturnSelf(); - $selectMock->expects($this->any())->method('distinct')->willReturnSelf(); - $selectMock->expects($this->any())->method('where')->willReturnSelf(); - $selectMock->expects($this->any())->method('columns')->willReturnSelf(); - $selectMock->expects($this->any())->method('joinLeft')->willReturnSelf(); - $selectMock->expects($this->any())->method('group')->willReturnSelf(); - $this->adapterMock->expects($this->any())->method('select')->willReturn($selectMock); - $tableMock = $this->createMock(Table::class); - $this->model->getDataSet($bucketMock, ['scope' => $dimensionMock], $tableMock); + $this->adapterMock->expects($this->once())->method('fetchAssoc')->with($selectMock); + + $this->model->execute($selectMock); } } diff --git a/app/code/Magento/Directory/Setup/UpgradeData.php b/app/code/Magento/Directory/Setup/UpgradeData.php index aa0f81a32fff06a84eeadc3636d1ef01b3f9b1d7..4ee9ea33673d736e253b7ac3b373365c5b9d6439 100644 --- a/app/code/Magento/Directory/Setup/UpgradeData.php +++ b/app/code/Magento/Directory/Setup/UpgradeData.php @@ -41,23 +41,21 @@ class UpgradeData implements UpgradeDataInterface public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { if (version_compare($context->getVersion(), '2.0.1', '<')) { - $this->addCroatia($setup); + $this->addCountryRegions($setup, $this->getDataForCroatia()); + } + if (version_compare($context->getVersion(), '2.0.2', '<')) { + $this->addCountryRegions($setup, $this->getDataForIndia()); } } /** - * Add Croatia and it's states to appropriate tables. + * Croatian states data. * - * @param ModuleDataSetupInterface $setup - * @return void + * @return array */ - private function addCroatia($setup) + private function getDataForCroatia() { - /** - * Fill table directory/country_region - * Fill table directory/country_region_name for en_US locale - */ - $data = [ + return [ ['HR', 'HR-01', 'ZagrebaÄka županija'], ['HR', 'HR-02', 'Krapinsko-zagorska županija'], ['HR', 'HR-03', 'SisaÄko-moslavaÄka županija'], @@ -80,6 +78,68 @@ class UpgradeData implements UpgradeDataInterface ['HR', 'HR-20', 'MeÄ‘imurska županija'], ['HR', 'HR-21', 'Grad Zagreb'] ]; + } + + /** + * Indian states data. + * + * @return array + */ + private function getDataForIndia() + { + return [ + ['IN', 'AN', 'Andaman and Nicobar Islands'], + ['IN', 'AP', 'Andhra Pradesh'], + ['IN', 'AR', 'Arunachal Pradesh'], + ['IN', 'AS', 'Assam'], + ['IN', 'BR', 'Bihar'], + ['IN', 'CH', 'Chandigarh'], + ['IN', 'CT', 'Chhattisgarh'], + ['IN', 'DN', 'Dadra and Nagar Haveli'], + ['IN', 'DD', 'Daman and Diu'], + ['IN', 'DL', 'Delhi'], + ['IN', 'GA', 'Goa'], + ['IN', 'GJ', 'Gujarat'], + ['IN', 'HR', 'Haryana'], + ['IN', 'HP', 'Himachal Pradesh'], + ['IN', 'JK', 'Jammu and Kashmir'], + ['IN', 'JH', 'Jharkhand'], + ['IN', 'KA', 'Karnataka'], + ['IN', 'KL', 'Kerala'], + ['IN', 'LD', 'Lakshadweep'], + ['IN', 'MP', 'Madhya Pradesh'], + ['IN', 'MH', 'Maharashtra'], + ['IN', 'MN', 'Manipur'], + ['IN', 'ML', 'Meghalaya'], + ['IN', 'MZ', 'Mizoram'], + ['IN', 'NL', 'Nagaland'], + ['IN', 'OR', 'Odisha'], + ['IN', 'PY', 'Puducherry'], + ['IN', 'PB', 'Punjab'], + ['IN', 'RJ', 'Rajasthan'], + ['IN', 'SK', 'Sikkim'], + ['IN', 'TN', 'Tamil Nadu'], + ['IN', 'TG', 'Telangana'], + ['IN', 'TR', 'Tripura'], + ['IN', 'UP', 'Uttar Pradesh'], + ['IN', 'UT', 'Uttarakhand'], + ['IN', 'WB', 'West Bengal'] + ]; + } + + /** + * Add country regions data to appropriate tables. + * + * @param ModuleDataSetupInterface $setup + * @param array $data + * @return void + */ + private function addCountryRegions(ModuleDataSetupInterface $setup, array $data) + { + /** + * Fill table directory/country_region + * Fill table directory/country_region_name for en_US locale + */ foreach ($data as $row) { $bind = ['country_id' => $row[0], 'code' => $row[1], 'default_name' => $row[2]]; $setup->getConnection()->insert($setup->getTable('directory_country_region'), $bind); diff --git a/app/code/Magento/Directory/etc/module.xml b/app/code/Magento/Directory/etc/module.xml index 2711a91577a9ab7ad6dc8b8c764f3306ba2b57c4..a3735ca6ddde1113cd012aab75564db342336b7f 100644 --- a/app/code/Magento/Directory/etc/module.xml +++ b/app/code/Magento/Directory/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_Directory" setup_version="2.0.1"> + <module name="Magento_Directory" setup_version="2.0.2"> <sequence> <module name="Magento_Store"/> </sequence> diff --git a/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml b/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml index ec74b837e1077e145f9b2f0d4fc429b8abf9c191..a37306bf1eed7b64b0f78c0e60a8ed6664ca4e5e 100644 --- a/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml +++ b/app/code/Magento/Marketplace/view/adminhtml/templates/index.phtml @@ -46,9 +46,9 @@ </div> <div class="col-m-3"> <img - class="magento-connect-logo" + class="magento-marketplace-logo" src="<?php /* @escapeNotVerified */ echo $block - ->getViewFileUrl('Magento_Marketplace::partners/images/magento-connect.png'); + ->getViewFileUrl('Magento_Marketplace::partners/images/magento-marketplace.svg'); ?>" alt="Partner"/> </div> @@ -61,7 +61,7 @@ ); ?> </p> <a class="action-secondary" target="_blank" - href="http://www.magentocommerce.com/magento-connect/"> + href="https://marketplace.magento.com/"> <?= /* @escapeNotVerified */ __('Visit Magento Marketplaces') ?> </a> </div> diff --git a/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-connect.png b/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-connect.png deleted file mode 100644 index 575563f341b3550c8bd686eeeb0af8a8bc75690b..0000000000000000000000000000000000000000 Binary files a/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-connect.png and /dev/null differ diff --git a/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-marketplace.svg b/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-marketplace.svg new file mode 100644 index 0000000000000000000000000000000000000000..388544d5d7f3deef8a548fcca20c48a90869127a --- /dev/null +++ b/app/code/Magento/Marketplace/view/adminhtml/web/partners/images/magento-marketplace.svg @@ -0,0 +1 @@ +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 273.37 35.85"><defs><style>.cls-1{fill:#f26322;}.cls-2{fill:#4d4d4d;}</style></defs><title>Artboard 1</title><g id="Logo"><polygon class="cls-1" points="15.54 0 0 8.97 0 26.89 4.43 29.45 4.41 11.53 15.51 5.12 26.61 11.53 26.61 29.44 31.05 26.89 31.05 8.95 15.54 0"/><polygon class="cls-1" points="17.74 29.45 15.52 30.73 13.3 29.46 13.3 11.53 8.86 14.09 8.87 32.01 15.52 35.85 22.18 32.01 22.18 14.09 17.74 11.53 17.74 29.45"/><path class="cls-2" d="M41.21,9.08l6.1,15.41h0l6-15.41h2.32V26.74H54V11.35h0q-.12.42-.27.84l-.28.77c-.11.28-.2.54-.28.79L48,26.74H46.57l-5.16-13q-.15-.35-.3-.75t-.27-.78l-.3-.86h0V26.74H39V9.08Z"/><path class="cls-2" d="M60.3,26.81A3.76,3.76,0,0,1,59,26.14,3.14,3.14,0,0,1,58.1,25a3.54,3.54,0,0,1-.32-1.56,3.64,3.64,0,0,1,.42-1.85,3.24,3.24,0,0,1,1.14-1.15A5.74,5.74,0,0,1,61,19.82a17.86,17.86,0,0,1,2-.35q.94-.1,1.59-.21A4.84,4.84,0,0,0,65.69,19a1.27,1.27,0,0,0,.59-.46,1.41,1.41,0,0,0,.19-.78V17.5a2.33,2.33,0,0,0-.79-1.94,3.57,3.57,0,0,0-2.27-.63q-3.41,0-3.58,2.84H58.32a4.17,4.17,0,0,1,1.37-3,5.44,5.44,0,0,1,3.72-1.11,5.32,5.32,0,0,1,3.31.93,3.57,3.57,0,0,1,1.21,3v6.94a1.31,1.31,0,0,0,.21.83.83.83,0,0,0,.63.28l.26,0,.31-.07h.07v1.11a2.9,2.9,0,0,1-.42.14,2.61,2.61,0,0,1-.62.06A2,2,0,0,1,67,26.48a1.87,1.87,0,0,1-.54-1.37v-.27h-.07a7.46,7.46,0,0,1-.65.77,4.41,4.41,0,0,1-.93.72,5.18,5.18,0,0,1-1.26.52A6.14,6.14,0,0,1,62,27a5.92,5.92,0,0,1-1.65-.22m3.77-1.35a4.33,4.33,0,0,0,1.35-.85,3.46,3.46,0,0,0,1.09-2.49v-2.3a5.29,5.29,0,0,1-1.49.53q-.88.19-1.82.31t-1.51.26a3.8,3.8,0,0,0-1.2.43,2.21,2.21,0,0,0-.8.8,2.6,2.6,0,0,0-.3,1.32,2.36,2.36,0,0,0,.23,1.11,2,2,0,0,0,.62.72,2.42,2.42,0,0,0,.9.38,5.18,5.18,0,0,0,1.09.11,4.75,4.75,0,0,0,1.84-.33"/><path class="cls-2" d="M72,30.26a3.4,3.4,0,0,1-1.46-2.38h1.48a2.08,2.08,0,0,0,1.2,1.59,5.45,5.45,0,0,0,2.38.48,4.13,4.13,0,0,0,3-1,3.62,3.62,0,0,0,1-2.68v-2h-.07a5.28,5.28,0,0,1-1.65,1.65,4.56,4.56,0,0,1-2.4.57A5.35,5.35,0,0,1,73.24,26a5,5,0,0,1-1.73-1.31,5.87,5.87,0,0,1-1.1-2A8.28,8.28,0,0,1,70,20.12a7.9,7.9,0,0,1,.44-2.75,6.14,6.14,0,0,1,1.19-2,4.89,4.89,0,0,1,1.74-1.23,5.4,5.4,0,0,1,2.11-.42A4.52,4.52,0,0,1,78,14.3a5,5,0,0,1,1.61,1.64h.07V14h1.51V26.24A4.92,4.92,0,0,1,80,29.68a5.62,5.62,0,0,1-4.27,1.53,6.06,6.06,0,0,1-3.66-1m6.69-6.46a6.2,6.2,0,0,0,1-3.7A8.14,8.14,0,0,0,79.49,18a4.52,4.52,0,0,0-.77-1.62,3.5,3.5,0,0,0-1.3-1A4.18,4.18,0,0,0,75.61,15a3.47,3.47,0,0,0-3,1.41,6.13,6.13,0,0,0-1,3.75,7.81,7.81,0,0,0,.25,2,4.82,4.82,0,0,0,.74,1.61,3.49,3.49,0,0,0,1.23,1.06,3.78,3.78,0,0,0,1.75.38,3.6,3.6,0,0,0,3.14-1.41"/><path class="cls-2" d="M86.45,26.55a5.21,5.21,0,0,1-1.86-1.41A6.29,6.29,0,0,1,83.44,23a8.58,8.58,0,0,1-.4-2.65,8.12,8.12,0,0,1,.42-2.66,6.63,6.63,0,0,1,1.17-2.12,5.3,5.3,0,0,1,1.83-1.41,5.57,5.57,0,0,1,2.41-.51,5.27,5.27,0,0,1,2.58.58,4.83,4.83,0,0,1,1.7,1.56A6.38,6.38,0,0,1,94.08,18a12.26,12.26,0,0,1,.27,2.59H84.62a7.4,7.4,0,0,0,.31,2,5.06,5.06,0,0,0,.82,1.62,3.7,3.7,0,0,0,1.35,1.09,4.31,4.31,0,0,0,1.9.4A3.62,3.62,0,0,0,91.48,25a4.26,4.26,0,0,0,1.25-2.09H94.2a5.44,5.44,0,0,1-1.73,3A5.13,5.13,0,0,1,89,27.06a6.07,6.07,0,0,1-2.54-.51m6-8.89a4.34,4.34,0,0,0-.72-1.43,3.28,3.28,0,0,0-1.19-1,3.88,3.88,0,0,0-1.7-.35,4.07,4.07,0,0,0-1.72.35,3.67,3.67,0,0,0-1.27,1,4.74,4.74,0,0,0-.83,1.42,7,7,0,0,0-.41,1.78h8.1a6.75,6.75,0,0,0-.27-1.77"/><path class="cls-2" d="M97.78,14v2h0a5.25,5.25,0,0,1,1.69-1.59,4.93,4.93,0,0,1,2.58-.63,4.23,4.23,0,0,1,2.93,1,3.75,3.75,0,0,1,1.15,3v9.06h-1.53V17.82a2.69,2.69,0,0,0-.78-2.14,3.14,3.14,0,0,0-2.14-.68,4.28,4.28,0,0,0-1.53.27,4,4,0,0,0-1.26.75,3.46,3.46,0,0,0-.85,1.15,3.42,3.42,0,0,0-.31,1.46v8.1H96.25V14Z"/><path class="cls-2" d="M110.13,26.3a2.13,2.13,0,0,1-.67-1.77V15.23h-1.93V14h1.93V10H111V14h2.37v1.26H111v9.06a1.2,1.2,0,0,0,.31,1,1.41,1.41,0,0,0,.93.26,2.62,2.62,0,0,0,.56-.06,2.55,2.55,0,0,0,.46-.14h.07v1.31a4.28,4.28,0,0,1-1.41.22,2.77,2.77,0,0,1-1.78-.53"/><path class="cls-2" d="M117.9,26.55A5.35,5.35,0,0,1,116,25.14,6.33,6.33,0,0,1,114.86,23a8.85,8.85,0,0,1,0-5.31A6.34,6.34,0,0,1,116,15.59a5.36,5.36,0,0,1,1.86-1.41,5.87,5.87,0,0,1,2.48-.51,5.79,5.79,0,0,1,2.47.51,5.39,5.39,0,0,1,1.85,1.41,6.17,6.17,0,0,1,1.16,2.12,9.11,9.11,0,0,1,0,5.31,6.17,6.17,0,0,1-1.16,2.12,5.38,5.38,0,0,1-1.85,1.41,5.78,5.78,0,0,1-2.47.51,5.86,5.86,0,0,1-2.48-.51m4.36-1.2a3.85,3.85,0,0,0,1.36-1.16,5.24,5.24,0,0,0,.82-1.73,8.23,8.23,0,0,0,0-4.2,5.23,5.23,0,0,0-.82-1.73,3.84,3.84,0,0,0-1.36-1.16,4.43,4.43,0,0,0-3.77,0,4,4,0,0,0-1.36,1.16,5.1,5.1,0,0,0-.83,1.73,8.25,8.25,0,0,0,0,4.2,5.11,5.11,0,0,0,.83,1.73,4.22,4.22,0,0,0,5.13,1.16"/><path class="cls-2" d="M128.22,16.09a1.54,1.54,0,0,1-1.6-1.64,1.61,1.61,0,1,1,3.21,0,1.56,1.56,0,0,1-1.61,1.64m0-3.1a1.34,1.34,0,0,0-1.37,1.46,1.38,1.38,0,1,0,2.75,0A1.34,1.34,0,0,0,128.22,13m.47,2.34-.54-.78H128v.75h-.31V13.48h.55c.38,0,.64.19.64.53a.49.49,0,0,1-.37.5l.52.74Zm-.48-1.56H128v.54h.23c.2,0,.33-.08.33-.27s-.11-.27-.32-.27"/></g><path class="cls-2" d="M134.39,9.93h4.27l4.51,11.7,4.41-11.7h4.22V27.07H148.6V14.13L143.5,27.07h-1l-5.2-12.95V27.07h-2.88Z"/><path class="cls-2" d="M154.45,23.59c0-2.92,2.83-4,6.42-4h1.56V19c0-1.68-.58-2.52-2.28-2.52a2.09,2.09,0,0,0-2.4,2H155c.24-2.92,2.56-4.15,5.37-4.15s5,1.15,5,4.58v8.22h-2.85V25.54a4.36,4.36,0,0,1-3.84,1.77C156.35,27.31,154.45,26.21,154.45,23.59Zm8-.91V21.44H161c-2.21,0-3.62.5-3.62,2,0,1.05.58,1.75,2,1.75C161.12,25.22,162.44,24.29,162.44,22.68Z"/><path class="cls-2" d="M168.67,14.53h2.9v2.35a4.18,4.18,0,0,1,4.08-2.54v2.71c-2.54,0-4.08.84-4.08,3.5v6.52h-2.9Z"/><path class="cls-2" d="M178.19,8.73h2.9V20l4.48-5.51h3.16l-4.87,5.75,5.27,6.78h-3.36l-4.7-6.16v6.16h-2.9Z"/><path class="cls-2" d="M189.7,20.93v-.19a6.1,6.1,0,0,1,6.23-6.47c3.12,0,5.92,1.85,5.92,6.33v.84h-9.18c.1,2.37,1.29,3.71,3.45,3.71,1.75,0,2.66-.7,2.88-1.92h2.8c-.41,2.64-2.54,4.08-5.75,4.08A6,6,0,0,1,189.7,20.93ZM199,19.5c-.14-2.16-1.25-3.12-3-3.12s-2.92,1.17-3.21,3.12Z"/><path class="cls-2" d="M204.92,23.57V16.72h-1.68V14.53h1.68V11.78h2.9v2.76h2.76v2.18h-2.76v6.59c0,1.1.53,1.61,1.44,1.61a3.58,3.58,0,0,0,1.41-.24V27a5.57,5.57,0,0,1-2,.31C206.22,27.29,204.92,25.94,204.92,23.57Z"/><path class="cls-2" d="M213.29,14.53h2.9v2a4.84,4.84,0,0,1,4.1-2.28c3.14,0,5.56,2.33,5.56,6.38v.19c0,4-2.33,6.47-5.56,6.47a4.53,4.53,0,0,1-4.1-2.21v6.26h-2.9Zm9.59,6.35v-.19c0-2.78-1.44-4.15-3.33-4.15s-3.45,1.37-3.45,4.15v.19c0,2.8,1.37,4.12,3.48,4.12S222.88,23.57,222.88,20.89Z"/><path class="cls-2" d="M228.54,8.73h2.9V27.07h-2.9Z"/><path class="cls-2" d="M234,23.59c0-2.92,2.83-4,6.42-4H242V19c0-1.68-.58-2.52-2.28-2.52a2.09,2.09,0,0,0-2.4,2h-2.8c.24-2.92,2.56-4.15,5.37-4.15s5,1.15,5,4.58v8.22h-2.85V25.54a4.36,4.36,0,0,1-3.84,1.77C235.94,27.31,234,26.21,234,23.59Zm8-.91V21.44h-1.49c-2.21,0-3.62.5-3.62,2,0,1.05.58,1.75,2,1.75C240.71,25.22,242,24.29,242,22.68Z"/><path class="cls-2" d="M247.43,21v-.19a6.18,6.18,0,0,1,6.33-6.5c2.78,0,5.39,1.25,5.73,4.7h-2.8a2.59,2.59,0,0,0-2.88-2.37c-2,0-3.4,1.53-3.4,4.12v.19c0,2.73,1.34,4.17,3.48,4.17a2.85,2.85,0,0,0,3-2.68h2.66c-.22,2.88-2.4,4.91-5.8,4.91A6,6,0,0,1,247.43,21Z"/><path class="cls-2" d="M261.21,20.93v-.19a6.1,6.1,0,0,1,6.23-6.47c3.12,0,5.92,1.85,5.92,6.33v.84h-9.18c.1,2.37,1.29,3.71,3.45,3.71,1.75,0,2.66-.7,2.88-1.92h2.8c-.41,2.64-2.54,4.08-5.75,4.08A6,6,0,0,1,261.21,20.93Zm9.28-1.44c-.14-2.16-1.25-3.12-3-3.12s-2.92,1.17-3.21,3.12Z"/></svg> \ No newline at end of file diff --git a/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less index 6afe96c7f9ef8f93c834d731da1b60fdf436e9a7..d4f918567e579875133844f1b08774845fa956cf 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less @@ -61,7 +61,7 @@ } .partners-footer { - .magento-connect-logo { + .magento-marketplace-logo { float: right; margin-bottom: 1px; } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php index e48c5ad018db41b5176077df5f65fcb271df9fc6..00b20ae8eecdbc49e5a7c6c8cbedef97b737202d 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php @@ -7,7 +7,6 @@ namespace Magento\Catalog\Api; use Magento\Framework\Webapi\Exception as HTTPExceptionCodes; -use Magento\TestFramework\Helper\Bootstrap; class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract { @@ -194,6 +193,36 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web $this->assertEquals("Default Blue Updated", $result['options'][1]['label']); } + /** + * Test source model and backend type can not be changed to custom, as they depends on attribute frontend type. + * + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void + */ + public function testUpdateAttributeSourceAndType() + { + $attributeCode = uniqid('label_attr_code'); + $attribute = $this->createAttribute($attributeCode); + $attributeData = [ + 'attribute' => [ + 'attribute_id' => $attribute['attribute_id'], + 'attribute_code' => $attributeCode, + 'entity_type_id' => 4, + 'is_required' => false, + 'frontend_input' => 'select', + 'source_model' => "Some/Custom/Source/Model", + 'backend_type' => 'varchar', + 'frontend_labels' => [ + ['store_id' => 1, 'label' => 'front_lbl_new'], + ], + ], + ]; + + $result = $this->updateAttribute($attributeCode, $attributeData); + $this->assertEquals(\Magento\Eav\Model\Entity\Attribute\Source\Table::class, $result['source_model']); + $this->assertEquals('int', $result['backend_type']); + } + /** * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/CopierTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/CopierTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d3e45c6e1d54ef9e1b817623dfbc44fae9ffde86 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/CopierTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product; + +use Magento\Catalog\Model\ProductRepository; + +class CopierTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\Catalog\Model\Product\Copier + */ + private $copier; + + /** + * @var \Magento\Catalog\Model\ProductRepository + */ + private $productRepository; + + /** + * Tests multiple duplication of the same product. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ + public function testDoubleCopy() + { + $product = $this->productRepository->get('simple'); + + $product1 = $this->copier->copy($product); + $this->assertEquals( + 'simple-1', + $product1->getSku() + ); + $this->assertEquals( + 'simple-product-1', + $product1->getUrlKey() + ); + + $product2 = $this->copier->copy($product); + $this->assertEquals( + 'simple-2', + $product2->getSku() + ); + $this->assertEquals( + 'simple-product-2', + $product2->getUrlKey() + ); + } + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->copier = $this->objectManager->get(Copier::class); + $this->productRepository = $this->objectManager->get(ProductRepository::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $skus = [ + 'simple-1', + 'simple-2' + ]; + foreach ($skus as $sku) { + try { + $product = $this->productRepository->get($sku, false, null, true); + $this->productRepository->delete($product); + } catch (NoSuchEntityException $e) { + } + } + parent::tearDown(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1d2e090d1923ba592577f6b5aba715778de0c345 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Config; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Tests Magento\Framework\Config\Convert + */ +class ConverterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Converter + */ + private $converter; + + /** + * Tests config value "false" is not interpreted as true. + * + * @param string $sourceString + * @param array $expected + * @dataProvider parseVarElementDataProvider + */ + public function testParseVarElement($sourceString, $expected) + { + $document = new \DOMDocument(); + $document->loadXML($sourceString); + $actual = $this->converter->convert($document); + + self::assertEquals( + $expected, + $actual + ); + } + + /** + * Data provider for testParseVarElement. + * + * @return array + */ + public function parseVarElementDataProvider() + { + // @codingStandardsIgnoreStart + $sourceString = <<<'XML' +<?xml version="1.0"?> +<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/view.xsd"> + <vars module="Magento_Test"> + <var name="str">some string</var> + <var name="int-1">1</var> + <var name="int-0">0</var> + <var name="bool-true">true</var> + <var name="bool-false">false</var> + </vars> + </view> +XML; + // @codingStandardsIgnoreEnd + $expectedResult = [ + 'vars' => [ + 'Magento_Test' => [ + 'str' => 'some string', + 'int-1' => '1', + 'int-0' => '0', + 'bool-true' => true, + 'bool-false' => false + ] + ] + ]; + + return [ + [ + $sourceString, + $expectedResult + ], + ]; + } + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->converter = $this->objectManager->get(Converter::class); + } +} diff --git a/lib/internal/Magento/Framework/Config/Converter.php b/lib/internal/Magento/Framework/Config/Converter.php index 0401471f27ea509d15e395734a71cbe4415dfb0e..3e66f641b86977d127dc1d047529db08a6907d89 100644 --- a/lib/internal/Magento/Framework/Config/Converter.php +++ b/lib/internal/Magento/Framework/Config/Converter.php @@ -103,7 +103,9 @@ class Converter implements \Magento\Framework\Config\ConverterInterface } } if (!count($result)) { - $result = $node->nodeValue; + $result = (strtolower($node->nodeValue) !== 'true' && strtolower($node->nodeValue) !== 'false') + ? $node->nodeValue + : filter_var($node->nodeValue, FILTER_VALIDATE_BOOLEAN); } return $result; } diff --git a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php index 94d84dd0560df92adeb99c81d92fd627b1291288..93fe88a30f0650f0a400f8ad39322b7d5d80e7aa 100755 --- a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php @@ -147,6 +147,14 @@ class UiComponentFactory extends DataObject } $components = array_filter($components); $componentArguments['components'] = $components; + + /** + * Prevent passing ACL restricted blocks to htmlContent constructor + */ + if (isset($componentArguments['block']) && !$componentArguments['block']) { + return null; + } + if (!isset($componentArguments['context'])) { $componentArguments['context'] = $renderContext; } diff --git a/lib/internal/Magento/Framework/Webapi/Rest/Response/RendererFactory.php b/lib/internal/Magento/Framework/Webapi/Rest/Response/RendererFactory.php index c86b97b0fa8e7f8a78ad4b16a434ad69d012446c..547b8fcb03640c43e38f6b3c309d76cfc0b87b61 100644 --- a/lib/internal/Magento/Framework/Webapi/Rest/Response/RendererFactory.php +++ b/lib/internal/Magento/Framework/Webapi/Rest/Response/RendererFactory.php @@ -72,14 +72,9 @@ class RendererFactory $acceptTypes = [$acceptTypes]; } foreach ($acceptTypes as $acceptType) { - foreach ($this->_renders as $rendererConfig) { - $rendererType = $rendererConfig['type']; - if ($acceptType == $rendererType || $acceptType == current( - explode('/', $rendererType) - ) . '/*' || $acceptType == '*/*' - ) { - return $rendererConfig['model']; - } + $renderer = $this->getRendererConfig($acceptType); + if ($renderer !== null) { + return $renderer['model']; } } /** If server does not have renderer for any of the accepted types it SHOULD send 406 (not acceptable). */ @@ -93,4 +88,30 @@ class RendererFactory \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE ); } + + /** + * Get renderer config by accept type. + * + * @param string $acceptType + * @return array|null + */ + private function getRendererConfig($acceptType) + { + // If Accept type = '*/*' then return default renderer. + if ($acceptType == '*/*' && isset($this->_renders['default'])) { + return $this->_renders['default']; + } + + foreach ($this->_renders as $rendererConfig) { + $rendererType = $rendererConfig['type']; + if ($acceptType == $rendererType + || $acceptType == current(explode('/', $rendererType)) . '/*' + || $acceptType == '*/*' + ) { + return $rendererConfig; + } + } + + return null; + } } diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Rest/Response/RendererFactoryTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Rest/Response/RendererFactoryTest.php index ba460c5a5f6e66e7cd2d8efae56bd8ffa01d5ca4..dd7aa845f3091805a841f0324580f0d12634bc49 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/Rest/Response/RendererFactoryTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Rest/Response/RendererFactoryTest.php @@ -26,11 +26,18 @@ class RendererFactoryTest extends \PHPUnit\Framework\TestCase )->disableOriginalConstructor()->getMock(); $renders = [ - 'default' => ['type' => '*/*', 'model' => \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class], + 'application_xml' => [ + 'type' => 'application/xml', + 'model' => \Magento\Framework\Webapi\Rest\Response\Renderer\Xml::class, + ], 'application_json' => [ 'type' => 'application/json', 'model' => \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class, ], + 'default' => [ + 'type' => '*/*', + 'model' => \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class + ], ]; $this->_factory = new \Magento\Framework\Webapi\Rest\Response\RendererFactory( @@ -42,29 +49,43 @@ class RendererFactoryTest extends \PHPUnit\Framework\TestCase /** * Test GET method. + * + * @param array $acceptTypes + * @param string $model + * @dataProvider getTestDataProvider */ - public function testGet() + public function testGet($acceptTypes, $model) { - $acceptTypes = ['application/json']; - /** Mock request getAcceptTypes method to return specified value. */ $this->_requestMock->expects($this->once())->method('getAcceptTypes')->will($this->returnValue($acceptTypes)); /** Mock renderer. */ - $rendererMock = $this->getMockBuilder( - \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class - )->disableOriginalConstructor()->getMock(); + $rendererMock = $this->getMockBuilder($model)->disableOriginalConstructor()->getMock(); /** Mock object to return mocked renderer. */ $this->_objectManagerMock->expects( $this->once() )->method( 'get' )->with( - \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class + $model )->will( $this->returnValue($rendererMock) ); $this->_factory->get(); } + + /** + * Data provider for method testGet + * + * @return array + */ + public function getTestDataProvider() + { + return [ + [['*/*'], \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class], + [['application/json'], \Magento\Framework\Webapi\Rest\Response\Renderer\Json::class], + [['application/xml'], \Magento\Framework\Webapi\Rest\Response\Renderer\Xml::class], + ]; + } /** * Test GET method with wrong Accept HTTP Header.