Skip to content

Commit 9ba75c8

Browse files
authored
Merge pull request #2662 from magento-mpi/MPI-PR-060618
[MPI] Bug Fixes
2 parents c6dcbb0 + 7e853f8 commit 9ba75c8

File tree

8 files changed

+405
-2
lines changed

8 files changed

+405
-2
lines changed

app/code/Magento/Quote/Model/Quote.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1400,7 +1400,7 @@ public function getAllVisibleItems()
14001400
{
14011401
$items = [];
14021402
foreach ($this->getItemsCollection() as $item) {
1403-
if (!$item->isDeleted() && !$item->getParentItemId()) {
1403+
if (!$item->isDeleted() && !$item->getParentItemId() && !$item->getParentItem()) {
14041404
$items[] = $item;
14051405
}
14061406
}

app/code/Magento/Quote/Model/QuoteRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ protected function loadQuote($loadMethod, $loadField, $identifier, array $shared
212212
if ($sharedStoreIds) {
213213
$quote->setSharedStoreIds($sharedStoreIds);
214214
}
215-
$quote->$loadMethod($identifier)->setStoreId($this->storeManager->getStore()->getId());
215+
$quote->setStoreId($this->storeManager->getStore()->getId())->$loadMethod($identifier);
216216
if (!$quote->getId()) {
217217
throw NoSuchEntityException::singleField($loadField, $identifier);
218218
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Plugin;
9+
10+
use Magento\Checkout\Model\Session;
11+
use Magento\Quote\Model\QuoteRepository;
12+
use Magento\Store\Api\Data\StoreInterface;
13+
use Magento\Store\Api\StoreCookieManagerInterface;
14+
15+
/**
16+
* Updates quote store id.
17+
*/
18+
class UpdateQuoteStore
19+
{
20+
/**
21+
* @var QuoteRepository
22+
*/
23+
private $quoteRepository;
24+
25+
/**
26+
* @var Session
27+
*/
28+
private $checkoutSession;
29+
30+
/**
31+
* @param QuoteRepository $quoteRepository
32+
* @param Session $checkoutSession
33+
*/
34+
public function __construct(
35+
QuoteRepository $quoteRepository,
36+
Session $checkoutSession
37+
) {
38+
$this->quoteRepository = $quoteRepository;
39+
$this->checkoutSession = $checkoutSession;
40+
}
41+
42+
/**
43+
* Update store id in active quote after store view switching.
44+
*
45+
* @param StoreCookieManagerInterface $subject
46+
* @param null $result
47+
* @param StoreInterface $store
48+
* @return void
49+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
50+
*/
51+
public function afterSetStoreCookie(
52+
StoreCookieManagerInterface $subject,
53+
$result,
54+
StoreInterface $store
55+
) {
56+
$storeCodeFromCookie = $subject->getStoreCodeFromCookie();
57+
if (null === $storeCodeFromCookie) {
58+
return;
59+
}
60+
61+
$quote = $this->checkoutSession->getQuote();
62+
if ($quote->getIsActive() && $store->getCode() != $storeCodeFromCookie) {
63+
$quote->setStoreId(
64+
$store->getId()
65+
);
66+
$this->quoteRepository->save($quote);
67+
}
68+
}
69+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
9+
<type name="Magento\Quote\Plugin\UpdateQuoteStore">
10+
<arguments>
11+
<argument name="quoteRepository" xsi:type="object">Magento\Quote\Model\QuoteRepository\Proxy</argument>
12+
<argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument>
13+
</arguments>
14+
</type>
15+
<type name="Magento\Store\Api\StoreCookieManagerInterface">
16+
<plugin name="update_quote_store_after_switch_store_view" type="Magento\Quote\Plugin\UpdateQuoteStore"/>
17+
</type>
18+
</config>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Model\Plugin;
9+
10+
use Magento\Checkout\Model\Session;
11+
use Magento\Framework\Api\SearchCriteriaBuilder;
12+
use Magento\FunctionalTestingFramework\ObjectManagerInterface;
13+
use Magento\Quote\Api\CartRepositoryInterface;
14+
use Magento\Quote\Model\Quote;
15+
use Magento\Store\Api\Data\StoreInterface;
16+
use Magento\Store\Api\StoreCookieManagerInterface;
17+
use Magento\Store\Model\StoreManagerInterface;
18+
use Magento\Store\Model\StoreRepository;
19+
use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper;
20+
21+
/**
22+
* @magentoAppArea frontend
23+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
24+
*/
25+
class UpdateQuoteStoreTest extends \PHPUnit\Framework\TestCase
26+
{
27+
/**
28+
* @var ObjectManagerInterface
29+
*/
30+
private $objectManager;
31+
32+
/**
33+
* @var CartRepositoryInterface
34+
*/
35+
private $quoteRepository;
36+
37+
protected function setUp()
38+
{
39+
$this->objectManager = BootstrapHelper::getObjectManager();
40+
$this->quoteRepository = $this->objectManager->create(CartRepositoryInterface::class);
41+
}
42+
43+
/**
44+
* Tests that active quote store id updates after store cookie change.
45+
*
46+
* @magentoDataFixture Magento/Quote/_files/empty_quote.php
47+
* @magentoDataFixture Magento/Store/_files/second_store.php
48+
* @throws \ReflectionException
49+
* @throws \Magento\Framework\Exception\NoSuchEntityException
50+
*/
51+
public function testUpdateQuoteStoreAfterChangeStoreCookie()
52+
{
53+
$secondStoreCode = 'fixture_second_store';
54+
$reservedOrderId = 'reserved_order_id';
55+
56+
/** @var StoreManagerInterface $storeManager */
57+
$storeManager = $this->objectManager->get(StoreManagerInterface::class);
58+
$currentStore = $storeManager->getStore();
59+
60+
$quote = $this->getQuote($reservedOrderId);
61+
$this->assertEquals(
62+
$currentStore->getId(),
63+
$quote->getStoreId(),
64+
'Current store id and quote store id are not match'
65+
);
66+
67+
/** @var Session $checkoutSession */
68+
$checkoutSession = $this->objectManager->get(Session::class);
69+
$checkoutSession->setQuoteId($quote->getId());
70+
71+
$storeRepository = $this->objectManager->create(StoreRepository::class);
72+
$secondStore = $storeRepository->get($secondStoreCode);
73+
74+
$storeCookieManager = $this->getStoreCookieManager($currentStore);
75+
$storeCookieManager->setStoreCookie($secondStore);
76+
77+
$updatedQuote = $this->getQuote($reservedOrderId);
78+
$this->assertEquals(
79+
$secondStore->getId(),
80+
$updatedQuote->getStoreId(),
81+
'Active quote store id should be equal second store id'
82+
);
83+
}
84+
85+
/**
86+
* Retrieves quote by reserved order id.
87+
*
88+
* @param string $reservedOrderId
89+
* @return Quote
90+
*/
91+
private function getQuote(string $reservedOrderId): Quote
92+
{
93+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
94+
$searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class);
95+
$searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId)
96+
->create();
97+
98+
$items = $this->quoteRepository->getList($searchCriteria)->getItems();
99+
100+
return array_pop($items);
101+
}
102+
103+
/**
104+
* Returns instance of StoreCookieManagerInterface with mocked cookieManager dependency.
105+
*
106+
* Mock is needed since integration test framework use own cookie manager with
107+
* behavior that differs from real environment.
108+
*
109+
* @param $currentStore
110+
* @return StoreCookieManagerInterface
111+
* @throws \ReflectionException
112+
*/
113+
private function getStoreCookieManager(StoreInterface $currentStore): StoreCookieManagerInterface
114+
{
115+
/** @var StoreCookieManagerInterface $storeCookieManager */
116+
$storeCookieManager = $this->objectManager->get(StoreCookieManagerInterface::class);
117+
$cookieManagerMock = $this->getMockBuilder(\Magento\Framework\Stdlib\Cookie\PhpCookieManager::class)
118+
->disableOriginalConstructor()
119+
->getMock();
120+
$cookieManagerMock->method('getCookie')
121+
->willReturn($currentStore->getCode());
122+
123+
$reflection = new \ReflectionClass($storeCookieManager);
124+
$cookieManager = $reflection->getProperty('cookieManager');
125+
$cookieManager->setAccessible(true);
126+
$cookieManager->setValue($storeCookieManager, $cookieManagerMock);
127+
128+
return $storeCookieManager;
129+
}
130+
}

dev/tests/integration/testsuite/Magento/Quote/Model/QuoteRepositoryTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\Quote\Model;
77

8+
use Magento\Store\Model\StoreRepository;
89
use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper;
910
use Magento\Framework\ObjectManagerInterface;
1011
use Magento\Framework\Api\SearchCriteriaBuilder;
@@ -50,6 +51,34 @@ protected function setUp()
5051
$this->filterBuilder = $this->objectManager->create(FilterBuilder::class);
5152
}
5253

54+
/**
55+
* Tests that quote saved with custom store id has same store id after getting via repository.
56+
*
57+
* @magentoDataFixture Magento/Sales/_files/quote.php
58+
* @magentoDataFixture Magento/Store/_files/second_store.php
59+
*/
60+
public function testGetQuoteWithCustomStoreId()
61+
{
62+
$secondStoreCode = 'fixture_second_store';
63+
$reservedOrderId = 'test01';
64+
65+
$storeRepository = $this->objectManager->create(StoreRepository::class);
66+
$secondStore = $storeRepository->get($secondStoreCode);
67+
68+
// Set store_id in quote to second store_id
69+
$quote = $this->getQuote($reservedOrderId);
70+
$quote->setStoreId($secondStore->getId());
71+
$this->quoteRepository->save($quote);
72+
73+
$savedQuote = $this->quoteRepository->get($quote->getId());
74+
75+
$this->assertEquals(
76+
$secondStore->getId(),
77+
$savedQuote->getStoreId(),
78+
'Quote store id should be equal with store id value in DB'
79+
);
80+
}
81+
5382
/**
5483
* @magentoDataFixture Magento/Sales/_files/quote.php
5584
*/
@@ -126,6 +155,24 @@ public function testSaveWithNotExistingCustomerAddress()
126155
);
127156
}
128157

158+
/**
159+
* Returns quote by reserved order id.
160+
*
161+
* @param string $reservedOrderId
162+
* @return CartInterface
163+
*/
164+
private function getQuote(string $reservedOrderId)
165+
{
166+
$searchCriteria = $this->getSearchCriteria($reservedOrderId);
167+
$searchResult = $this->quoteRepository->getList($searchCriteria);
168+
$items = $searchResult->getItems();
169+
170+
/** @var CartInterface $quote */
171+
$quote = array_pop($items);
172+
173+
return $quote;
174+
}
175+
129176
/**
130177
* Get search criteria
131178
*

dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77
namespace Magento\SalesRule\Model\Rule\Condition;
88

9+
use Magento\Quote\Api\CartRepositoryInterface;
10+
use Magento\Framework\Api\SearchCriteriaBuilder;
11+
use Magento\Quote\Api\Data\CartInterface;
12+
use Magento\SalesRule\Api\RuleRepositoryInterface;
13+
14+
/**
15+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
16+
*/
917
class ProductTest extends \PHPUnit\Framework\TestCase
1018
{
1119
/**
@@ -94,4 +102,71 @@ public function validateProductConditionDataProvider()
94102
]
95103
];
96104
}
105+
106+
/**
107+
* Ensure that SalesRules filtering on quote items quantity validates configurable product correctly
108+
*
109+
* 1. Load a quote with a configured product and a sales rule set to filter items with quantity 2.
110+
* 2. Attempt to validate the sales rule against the quote and assert the output is negative.
111+
*
112+
* @magentoDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php
113+
* @magentoDataFixture Magento/SalesRule/_files/cart_rule_10_percent_off.php
114+
*/
115+
public function testValidateQtySalesRuleWithConfigurable()
116+
{
117+
// Load the quote that contains a child of a configurable product with quantity 1.
118+
$quote = $this->getQuote('test_cart_with_configurable');
119+
120+
// Load the SalesRule looking for products with quantity 2.
121+
$rule = $this->getSalesRule('10% Off on orders with two items');
122+
123+
$this->assertFalse(
124+
$rule->validate($quote->getBillingAddress())
125+
);
126+
}
127+
128+
/**
129+
* Gets quote by reserved order id.
130+
*
131+
* @param string $reservedOrderId
132+
* @return CartInterface
133+
*/
134+
private function getQuote($reservedOrderId)
135+
{
136+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
137+
$searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class);
138+
$searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId)
139+
->create();
140+
141+
/** @var CartRepositoryInterface $quoteRepository */
142+
$quoteRepository = $this->objectManager->get(CartRepositoryInterface::class);
143+
$items = $quoteRepository->getList($searchCriteria)->getItems();
144+
return array_pop($items);
145+
}
146+
147+
/**
148+
* Gets rule by name.
149+
*
150+
* @param string $name
151+
* @return \Magento\SalesRule\Model\Rule
152+
* @throws \Magento\Framework\Exception\InputException
153+
* @throws \Magento\Framework\Exception\NoSuchEntityException
154+
*/
155+
private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule
156+
{
157+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
158+
$searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class);
159+
$searchCriteria = $searchCriteriaBuilder->addFilter('name', $name)
160+
->create();
161+
162+
/** @var CartRepositoryInterface $quoteRepository */
163+
$ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class);
164+
$items = $ruleRepository->getList($searchCriteria)->getItems();
165+
166+
$rule = array_pop($items);
167+
/** @var \Magento\SalesRule\Model\Converter\ToModel $converter */
168+
$converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class);
169+
170+
return $converter->toModel($rule);
171+
}
97172
}

0 commit comments

Comments
 (0)