diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php index 95d83e06c9809..1f01400468984 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php @@ -1,6 +1,6 @@ getTable('cataloginventory_stock_item'); $select = $this->getConnection()->select()->from(['si' => $itemTable]) - ->where('website_id=?', $websiteId) + ->where('website_id = ?', $websiteId) ->where('product_id IN(?)', $productIds) ->forUpdate(true); @@ -139,9 +139,15 @@ public function lockProductsStock($productIds, $websiteId) 'type_id' => 'type_id' ] ); - $this->getConnection()->query($select); + $items = []; - return $this->getConnection()->fetchAll($selectProducts); + foreach ($this->getConnection()->query($select)->fetchAll() as $si) { + $items[$si['product_id']] = $si; + } + foreach ($this->getConnection()->fetchAll($selectProducts) as $p) { + $items[$p['product_id']]['type_id'] = $p['type_id']; + } + return $items; } /** diff --git a/app/code/Magento/CatalogInventory/Model/StockManagement.php b/app/code/Magento/CatalogInventory/Model/StockManagement.php index 3055c1873b1c1..5716e35961236 100644 --- a/app/code/Magento/CatalogInventory/Model/StockManagement.php +++ b/app/code/Magento/CatalogInventory/Model/StockManagement.php @@ -1,6 +1,6 @@ stockRegistryProvider = $stockRegistryProvider; $this->stockState = $stockState; @@ -70,6 +77,8 @@ public function __construct( $this->productRepository = $productRepository; $this->qtyCounter = $qtyCounter; $this->resource = $stockResource; + $this->stockRegistryStorage = $stockRegistryStorage ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(StockRegistryStorage::class); } /** @@ -92,9 +101,12 @@ public function registerProductsSale($items, $websiteId = null) $fullSaveItems = $registeredItems = []; foreach ($lockedItems as $lockedItemRecord) { $productId = $lockedItemRecord['product_id']; + $this->stockRegistryStorage->removeStockItem($productId, $websiteId); + /** @var StockItemInterface $stockItem */ $orderedQty = $items[$productId]; $stockItem = $this->stockRegistryProvider->getStockItem($productId, $websiteId); + $stockItem->setQty($lockedItemRecord['qty']); // update data from locked item $canSubtractQty = $stockItem->getItemId() && $this->canSubtractQty($stockItem); if (!$canSubtractQty || !$this->stockConfiguration->isQty($lockedItemRecord['type_id'])) { continue; @@ -180,7 +192,7 @@ protected function getProductType($productId) } /** - * @return Stock + * @return ResourceStock */ protected function getResource() { diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php index f704387a685ce..371dfa6280f94 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php @@ -1,6 +1,6 @@ connectionMock = $this->getMockBuilder(Mysql::class) ->disableOriginalConstructor() ->getMock(); + $this->statementMock = $this->getMockForAbstractClass(\Zend_Db_Statement_Interface::class); $this->stock = $this->getMockBuilder(Stock::class) ->setMethods(['getTable', 'getConnection']) ->setConstructorArgs( @@ -119,7 +125,21 @@ public function testLockProductsStock() { $websiteId = 0; $productIds = [1, 2, 3]; - $result = ['testResult']; + $result = [ + 1 => [ + 'product_id' => 1, + 'type_id' => 'simple' + ], + 2 => [ + 'product_id' => 2, + 'type_id' => 'simple' + ], + 3 => [ + 'product_id' => 3, + 'type_id' => 'simple' + ] + ]; + $this->selectMock->expects(self::exactly(2)) ->method('from') ->withConsecutive( @@ -130,7 +150,7 @@ public function testLockProductsStock() $this->selectMock->expects(self::exactly(3)) ->method('where') ->withConsecutive( - [self::identicalTo('website_id=?'), self::identicalTo($websiteId)], + [self::identicalTo('website_id = ?'), self::identicalTo($websiteId)], [self::identicalTo('product_id IN(?)'), self::identicalTo($productIds)], [self::identicalTo('entity_id IN (?)'), self::identicalTo($productIds)] ) @@ -149,10 +169,19 @@ public function testLockProductsStock() ->willReturn($this->selectMock); $this->connectionMock->expects(self::once()) ->method('query') - ->with(self::identicalTo($this->selectMock)); + ->with(self::identicalTo($this->selectMock)) + ->willReturn($this->statementMock); + $this->statementMock->expects(self::once()) + ->method('fetchAll') + ->willReturn([ + 1 => ['product_id' => 1], + 2 => ['product_id' => 2], + 3 => ['product_id' => 3] + ]); + $this->connectionMock->expects(self::once()) ->method('fetchAll') - ->with($this->selectMock) + ->with(self::identicalTo($this->selectMock)) ->willReturn($result); $this->stock->expects(self::exactly(2)) diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php index 52a2c6ff40c99..0a787254ef57b 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php @@ -1,6 +1,6 @@ getField() === 'category_ids') { - return 'category_ids_index.category_id = ' . (int) $filter->getValue(); + return "{$this->aliasResolver->getAlias($filter)}.category_id = " + . (int) $filter->getValue(); + } elseif ($filter->getField() === 'visibility') { + return "{$this->aliasResolver->getAlias($filter)}." . $query; } elseif ($attribute->isStatic()) { $alias = $this->aliasResolver->getAlias($filter); $resultQuery = str_replace( diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 1f7d94b9c5f39..a255b33c28727 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -1,6 +1,6 @@ getSearchCriteriaBuilder(); $this->getFilterBuilder(); - if (!is_array($condition) || !in_array(key($condition), ['from', 'to'])) { - $this->filterBuilder->setField($field); - $this->filterBuilder->setValue($condition); - $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); - } else { + if (is_array($condition) + && in_array(key($condition), ['from', 'to'], true) + ) { if (!empty($condition['from'])) { - $this->filterBuilder->setField("{$field}.from"); - $this->filterBuilder->setValue($condition['from']); - $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); + $this->addFieldToFilter("{$field}.from", $condition['from']); } if (!empty($condition['to'])) { - $this->filterBuilder->setField("{$field}.to"); - $this->filterBuilder->setValue($condition['to']); - $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); + $this->addFieldToFilter("{$field}.to", $condition['to']); } + } else { + $this->filterBuilder->setField($field); + $this->filterBuilder->setValue($condition); + $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); } + return $this; } diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php index 03675f27cac21..59fbc3883c492 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php @@ -1,6 +1,6 @@ aliasResolver->getAlias($filter); - $tableName = $this->resourceConnection->getTableName('catalog_category_product_index'); - $select->joinInner( - [$alias => $tableName], - 'search_index.entity_id = category_ids_index.product_id', - [] - ); + if (!array_key_exists($alias, $select->getPart('from'))) { + $tableName = $this->resourceConnection->getTableName( + 'catalog_category_product_index' + ); + $select->joinInner( + [$alias => $tableName], + "search_index.entity_id = $alias.product_id", + [] + ); + } $isApplied = true; } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php index cb7b12fc698ef..52ac2049e14d5 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php @@ -1,6 +1,6 @@ [ 'field' => 'category_ids', - 'alias' => 'category_ids_index', + 'alias' => 'category_products_index', + ], + 'visibility' => [ + 'field' => 'visibility', + 'alias' => 'category_products_index', ], ]; } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php index c8dd43c74a10d..b78a29f75fe85 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php @@ -1,6 +1,6 @@ aliasResolver->expects($this->atLeastOnce()) + ->method('getAlias') + ->willReturn($tableAlias); $this->filter->expects($this->exactly(3)) ->method('getField') ->will($this->returnValue('category_ids')); @@ -233,9 +237,35 @@ public function testProcessCategoryIds($categoryId, $expectedResult) ->will($this->returnValue($this->attribute)); $actualResult = $this->target->process($this->filter, $isNegation, $query); + $expectedResult = strtr($expectedResult, [':alias' => $tableAlias]); $this->assertSame($expectedResult, $this->removeWhitespaces($actualResult)); } + public function testProcessVisibilityIds() + { + $query = 'visibility in (1, 2)'; + $tableAlias = 'visibility__alias'; + + $this->aliasResolver->expects($this->atLeastOnce()) + ->method('getAlias') + ->willReturn($tableAlias); + $this->filter->expects($this->atLeastOnce()) + ->method('getField') + ->will($this->returnValue('visibility')); + $this->filter->expects($this->never()) + ->method('getValue'); + $this->config->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'visibility') + ->will($this->returnValue($this->attribute)); + + $actualResult = $this->target->process($this->filter, false, $query); + $this->assertSame( + "$tableAlias.$query", + $this->removeWhitespaces($actualResult) + ); + } + public function testProcessStaticAttribute() { $expectedResult = 'attr_table_alias.static_attribute LIKE %name%'; @@ -246,7 +276,7 @@ public function testProcessStaticAttribute() ->willReturn('static_attribute'); $this->aliasResolver->expects($this->once())->method('getAlias') ->willReturn('attr_table_alias'); - $this->filter->expects($this->exactly(3)) + $this->filter->expects($this->exactly(4)) ->method('getField') ->will($this->returnValue('static_attribute')); $this->config->expects($this->exactly(1)) @@ -285,7 +315,7 @@ public function testProcessTermFilter($frontendInput, $fieldValue, $isNegation, $this->aliasResolver->expects($this->once())->method('getAlias') ->willReturn('termAttrAlias'); - $this->filter->expects($this->exactly(3)) + $this->filter->expects($this->exactly(4)) ->method('getField') ->willReturn('termField'); $this->filter->expects($this->exactly(2)) @@ -360,7 +390,7 @@ public function testProcessNotStaticAttribute() $attributeId = 1234567; $this->scope->expects($this->once())->method('getId')->will($this->returnValue($scopeId)); - $this->filter->expects($this->exactly(4)) + $this->filter->expects($this->exactly(5)) ->method('getField') ->will($this->returnValue('not_static_attribute')); $this->config->expects($this->exactly(1)) diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php index fc695987481a0..33b57216471f6 100644 --- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php @@ -1,11 +1,13 @@ billingAddressManagement = $billingAddressManagement; $this->paymentMethodManagement = $paymentMethodManagement; @@ -74,6 +83,7 @@ public function __construct( $this->paymentInformationManagement = $paymentInformationManagement; $this->quoteIdMaskFactory = $quoteIdMaskFactory; $this->cartRepository = $cartRepository; + $this->connectionPull = $connectionPull ?: ObjectManager::getInstance()->get(ResourceConnection::class); } /** @@ -85,20 +95,33 @@ public function savePaymentInformationAndPlaceOrder( \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, \Magento\Quote\Api\Data\AddressInterface $billingAddress = null ) { - $this->savePaymentInformation($cartId, $email, $paymentMethod, $billingAddress); + $salesConnection = $this->connectionPull->getConnection('sales'); + $checkoutConnection = $this->connectionPull->getConnection('checkout'); + $salesConnection->beginTransaction(); + $checkoutConnection->beginTransaction(); + try { - $orderId = $this->cartManagement->placeOrder($cartId); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - throw new CouldNotSaveException( - __($e->getMessage()), - $e - ); + $this->savePaymentInformation($cartId, $email, $paymentMethod, $billingAddress); + try { + $orderId = $this->cartManagement->placeOrder($cartId); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + throw new CouldNotSaveException( + __($e->getMessage()), + $e + ); + } catch (\Exception $e) { + $this->getLogger()->critical($e); + throw new CouldNotSaveException( + __('An error occurred on the server. Please try to place the order again.'), + $e + ); + } + $salesConnection->commit(); + $checkoutConnection->commit(); } catch (\Exception $e) { - $this->getLogger()->critical($e); - throw new CouldNotSaveException( - __('An error occurred on the server. Please try to place the order again.'), - $e - ); + $salesConnection->rollBack(); + $checkoutConnection->rollBack(); + throw $e; } return $orderId; diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php index 6e809d089d773..fbea3bad81dc7 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php @@ -1,11 +1,14 @@ loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = $objectManager->getObject( \Magento\Checkout\Model\GuestPaymentInformationManagement::class, [ @@ -73,7 +85,8 @@ protected function setUp() 'paymentMethodManagement' => $this->paymentMethodManagementMock, 'cartManagement' => $this->cartManagementMock, 'cartRepository' => $this->cartRepositoryMock, - 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock + 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock, + 'connectionPull' => $this->resourceConnectionMock ] ); $objectManager->setBackwardCompatibleProperty($this->model, 'logger', $this->loggerMock); @@ -89,6 +102,27 @@ public function testSavePaymentInformationAndPlaceOrder() $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); + $adapterMockForSales = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $adapterMockForCheckout = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getConnection') + ->with('sales') + ->willReturn($adapterMockForSales); + $adapterMockForSales->expects($this->once())->method('beginTransaction'); + $adapterMockForSales->expects($this->once())->method('commit'); + + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getConnection') + ->with('checkout') + ->willReturn($adapterMockForCheckout); + $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); + $adapterMockForCheckout->expects($this->once())->method('commit'); + $this->billingAddressManagementMock->expects($this->once()) ->method('assign') ->with($cartId, $billingAddressMock); @@ -114,6 +148,27 @@ public function testSavePaymentInformationAndPlaceOrderException() $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); + $adapterMockForSales = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $adapterMockForCheckout = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getConnection') + ->with('sales') + ->willReturn($adapterMockForSales); + $adapterMockForSales->expects($this->once())->method('beginTransaction'); + $adapterMockForSales->expects($this->once())->method('rollback'); + + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getConnection') + ->with('checkout') + ->willReturn($adapterMockForCheckout); + $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); + $adapterMockForCheckout->expects($this->once())->method('rollback'); + $this->billingAddressManagementMock->expects($this->once()) ->method('assign') ->with($cartId, $billingAddressMock); @@ -176,6 +231,27 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException() $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); + $adapterMockForSales = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $adapterMockForCheckout = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getConnection') + ->with('sales') + ->willReturn($adapterMockForSales); + $adapterMockForSales->expects($this->once())->method('beginTransaction'); + $adapterMockForSales->expects($this->once())->method('rollback'); + + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getConnection') + ->with('checkout') + ->willReturn($adapterMockForCheckout); + $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); + $adapterMockForCheckout->expects($this->once())->method('rollback'); + $this->billingAddressManagementMock->expects($this->once()) ->method('assign') ->with($cartId, $billingAddressMock); diff --git a/dev/tests/api-functional/testsuite/Magento/Framework/Api/Search/SearchTest.php b/dev/tests/api-functional/testsuite/Magento/Framework/Api/Search/SearchTest.php index eac7d409bfda2..175432b87c847 100644 --- a/dev/tests/api-functional/testsuite/Magento/Framework/Api/Search/SearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Framework/Api/Search/SearchTest.php @@ -1,6 +1,6 @@ 'price_dynamic_algorithm', 'value' => 'auto', 'condition_type' => 'eq' + ], + [ + 'field' => 'visibility', + 'value' => 4, + 'condition_type' => 'eq' ] ] ] diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php index aa70cfc3cd074..ff8fe18b1fd4a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/ResourceModel/Fulltext/CollectionTest.php @@ -1,6 +1,6 @@ 100001], 0], ['catalog_view_container', ['category_ids' => []], 0], ['catalog_view_container', [], 0], + ['catalog_view_container', ['visibility' => [2, 4]], 5], ]; } } diff --git a/lib/internal/Magento/Framework/Search/Search.php b/lib/internal/Magento/Framework/Search/Search.php index 9ad9d8b1500e2..b01d497d59d07 100644 --- a/lib/internal/Magento/Framework/Search/Search.php +++ b/lib/internal/Magento/Framework/Search/Search.php @@ -1,6 +1,6 @@ requestBuilder->bind($field, $condition); - } else { + if (is_array($condition) + && ( + !empty($condition['from']) || !empty($condition['to']) + ) + ) { if (!empty($condition['from'])) { $this->requestBuilder->bind("{$field}.from", $condition['from']); } if (!empty($condition['to'])) { $this->requestBuilder->bind("{$field}.to", $condition['to']); } + } else { + $this->requestBuilder->bind($field, $condition); } return $this;