Skip to content
This repository was archived by the owner on Apr 29, 2019. It is now read-only.

Commit 0cba9b4

Browse files
committed
Merge remote-tracking branch 'origin/2.3-develop-pr9' into 2.3-develop-pr9
2 parents 4506a8d + 90fd5e4 commit 0cba9b4

File tree

5 files changed

+363
-85
lines changed

5 files changed

+363
-85
lines changed

app/code/Magento/Sales/CustomerData/LastOrderedItems.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
namespace Magento\Sales\CustomerData;
77

88
use Magento\Customer\CustomerData\SectionSourceInterface;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Psr\Log\LoggerInterface;
912

1013
/**
1114
* Returns information for "Recently Ordered" widget.
@@ -54,25 +57,41 @@ class LastOrderedItems implements SectionSourceInterface
5457
*/
5558
private $_storeManager;
5659

60+
/**
61+
* @var \Magento\Catalog\Api\ProductRepositoryInterface
62+
*/
63+
private $productRepository;
64+
65+
/**
66+
* @var LoggerInterface
67+
*/
68+
private $logger;
69+
5770
/**
5871
* @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory
5972
* @param \Magento\Sales\Model\Order\Config $orderConfig
6073
* @param \Magento\Customer\Model\Session $customerSession
6174
* @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
6275
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
76+
* @param ProductRepositoryInterface $productRepository
77+
* @param LoggerInterface $logger
6378
*/
6479
public function __construct(
6580
\Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory,
6681
\Magento\Sales\Model\Order\Config $orderConfig,
6782
\Magento\Customer\Model\Session $customerSession,
6883
\Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
69-
\Magento\Store\Model\StoreManagerInterface $storeManager
84+
\Magento\Store\Model\StoreManagerInterface $storeManager,
85+
ProductRepositoryInterface $productRepository,
86+
LoggerInterface $logger
7087
) {
7188
$this->_orderCollectionFactory = $orderCollectionFactory;
7289
$this->_orderConfig = $orderConfig;
7390
$this->_customerSession = $customerSession;
7491
$this->stockRegistry = $stockRegistry;
7592
$this->_storeManager = $storeManager;
93+
$this->productRepository = $productRepository;
94+
$this->logger = $logger;
7695
}
7796

7897
/**
@@ -108,11 +127,23 @@ protected function getItems()
108127
$website = $this->_storeManager->getStore()->getWebsiteId();
109128
/** @var \Magento\Sales\Model\Order\Item $item */
110129
foreach ($order->getParentItemsRandomCollection($limit) as $item) {
111-
if ($item->hasData('product') && in_array($website, $item->getProduct()->getWebsiteIds())) {
130+
/** @var \Magento\Catalog\Model\Product $product */
131+
try {
132+
$product = $this->productRepository->getById(
133+
$item->getProductId(),
134+
false,
135+
$this->_storeManager->getStore()->getId()
136+
);
137+
} catch (NoSuchEntityException $noEntityException) {
138+
$this->logger->critical($noEntityException);
139+
continue;
140+
}
141+
if (isset($product) && in_array($website, $product->getWebsiteIds())) {
142+
$url = $product->isVisibleInSiteVisibility() ? $product->getProductUrl() : null;
112143
$items[] = [
113144
'id' => $item->getId(),
114145
'name' => $item->getName(),
115-
'url' => $item->getProduct()->getProductUrl(),
146+
'url' => $url,
116147
'is_saleable' => $this->isItemAvailableForReorder($item),
117148
];
118149
}
@@ -136,7 +167,7 @@ protected function isItemAvailableForReorder(\Magento\Sales\Model\Order\Item $or
136167
$orderItem->getStore()->getWebsiteId()
137168
);
138169
return $stockItem->getIsInStock();
139-
} catch (\Magento\Framework\Exception\NoSuchEntityException $noEntityException) {
170+
} catch (NoSuchEntityException $noEntityException) {
140171
return false;
141172
}
142173
}

app/code/Magento/Sales/Test/Unit/CustomerData/LastOrderedItemsTest.php

Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,21 @@ class LastOrderedItemsTest extends \PHPUnit\Framework\TestCase
4848
*/
4949
private $orderMock;
5050

51+
/**
52+
* @var \PHPUnit_Framework_MockObject_MockObject
53+
*/
54+
private $productRepositoryMock;
55+
5156
/**
5257
* @var \Magento\Sales\CustomerData\LastOrderedItems
5358
*/
5459
private $section;
5560

61+
/**
62+
* @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
63+
*/
64+
private $loggerMock;
65+
5666
protected function setUp()
5767
{
5868
$this->objectManagerHelper = new ObjectManagerHelper($this);
@@ -74,62 +84,97 @@ protected function setUp()
7484
$this->orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
7585
->disableOriginalConstructor()
7686
->getMock();
87+
$this->productRepositoryMock = $this->getMockBuilder(\Magento\Catalog\Api\ProductRepositoryInterface::class)
88+
->getMockForAbstractClass();
89+
$this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
90+
->getMockForAbstractClass();
91+
7792
$this->section = new \Magento\Sales\CustomerData\LastOrderedItems(
7893
$this->orderCollectionFactoryMock,
7994
$this->orderConfigMock,
8095
$this->customerSessionMock,
8196
$this->stockRegistryMock,
82-
$this->storeManagerMock
97+
$this->storeManagerMock,
98+
$this->productRepositoryMock,
99+
$this->loggerMock
83100
);
84101
}
85102

86103
public function testGetSectionData()
87104
{
105+
$storeId = 1;
88106
$websiteId = 4;
89-
$expectedItem = [
107+
$expectedItem1 = [
90108
'id' => 1,
91-
'name' => 'Product Name',
109+
'name' => 'Product Name 1',
92110
'url' => 'http://example.com',
93111
'is_saleable' => true,
94112
];
95-
$productId = 10;
113+
$expectedItem2 = [
114+
'id' => 2,
115+
'name' => 'Product Name 2',
116+
'url' => null,
117+
'is_saleable' => true,
118+
];
119+
$productIdVisible = 1;
120+
$productIdNotVisible = 2;
96121
$stockItemMock = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
97122
->getMockForAbstractClass();
98-
$itemWithProductMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
123+
$itemWithVisibleProduct = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
124+
->disableOriginalConstructor()
125+
->getMock();
126+
$itemWithNotVisibleProduct = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
99127
->disableOriginalConstructor()
100128
->getMock();
101-
$itemWithoutProductMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
129+
$productVisible = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
102130
->disableOriginalConstructor()
103131
->getMock();
104-
$productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
132+
$productNotVisible = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
105133
->disableOriginalConstructor()
106134
->getMock();
107-
$items = [$itemWithoutProductMock, $itemWithProductMock];
135+
$items = [$itemWithVisibleProduct, $itemWithNotVisibleProduct];
108136
$this->getLastOrderMock();
109137
$storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass();
110-
$this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock);
138+
$this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
111139
$storeMock->expects($this->any())->method('getWebsiteId')->willReturn($websiteId);
140+
$storeMock->expects($this->any())->method('getId')->willReturn($storeId);
112141
$this->orderMock->expects($this->once())
113142
->method('getParentItemsRandomCollection')
114143
->with(\Magento\Sales\CustomerData\LastOrderedItems::SIDEBAR_ORDER_LIMIT)
115144
->willReturn($items);
116-
$itemWithProductMock->expects($this->once())->method('hasData')->with('product')->willReturn(true);
117-
$itemWithProductMock->expects($this->any())->method('getProduct')->willReturn($productMock);
118-
$productMock->expects($this->once())->method('getWebsiteIds')->willReturn([1, 4]);
119-
$itemWithProductMock->expects($this->once())->method('getId')->willReturn($expectedItem['id']);
120-
$itemWithProductMock->expects($this->once())->method('getName')->willReturn($expectedItem['name']);
121-
$productMock->expects($this->once())->method('getProductUrl')->willReturn($expectedItem['url']);
122-
$this->stockRegistryMock->expects($this->once())->method('getStockItem')->willReturn($stockItemMock);
123-
$productMock->expects($this->once())->method('getId')->willReturn($productId);
124-
$itemWithProductMock->expects($this->once())->method('getStore')->willReturn($storeMock);
145+
$productVisible->expects($this->once())->method('isVisibleInSiteVisibility')->willReturn(true);
146+
$productVisible->expects($this->once())->method('getProductUrl')->willReturn($expectedItem1['url']);
147+
$productVisible->expects($this->once())->method('getWebsiteIds')->willReturn([1, 4]);
148+
$productVisible->expects($this->once())->method('getId')->willReturn($productIdVisible);
149+
$productNotVisible->expects($this->once())->method('isVisibleInSiteVisibility')->willReturn(false);
150+
$productNotVisible->expects($this->never())->method('getProductUrl');
151+
$productNotVisible->expects($this->once())->method('getWebsiteIds')->willReturn([1, 4]);
152+
$productNotVisible->expects($this->once())->method('getId')->willReturn($productIdNotVisible);
153+
$itemWithVisibleProduct->expects($this->once())->method('getProductId')->willReturn($productIdVisible);
154+
$itemWithVisibleProduct->expects($this->once())->method('getProduct')->willReturn($productVisible);
155+
$itemWithVisibleProduct->expects($this->once())->method('getId')->willReturn($expectedItem1['id']);
156+
$itemWithVisibleProduct->expects($this->once())->method('getName')->willReturn($expectedItem1['name']);
157+
$itemWithVisibleProduct->expects($this->once())->method('getStore')->willReturn($storeMock);
158+
$itemWithNotVisibleProduct->expects($this->once())->method('getProductId')->willReturn($productIdNotVisible);
159+
$itemWithNotVisibleProduct->expects($this->once())->method('getProduct')->willReturn($productNotVisible);
160+
$itemWithNotVisibleProduct->expects($this->once())->method('getId')->willReturn($expectedItem2['id']);
161+
$itemWithNotVisibleProduct->expects($this->once())->method('getName')->willReturn($expectedItem2['name']);
162+
$itemWithNotVisibleProduct->expects($this->once())->method('getStore')->willReturn($storeMock);
163+
$this->productRepositoryMock->expects($this->any())
164+
->method('getById')
165+
->willReturnMap([
166+
[$productIdVisible, false, $storeId, false, $productVisible],
167+
[$productIdNotVisible, false, $storeId, false, $productNotVisible],
168+
]);
125169
$this->stockRegistryMock
126-
->expects($this->once())
170+
->expects($this->any())
127171
->method('getStockItem')
128-
->with($productId, $websiteId)
129-
->willReturn($stockItemMock);
130-
$stockItemMock->expects($this->once())->method('getIsInStock')->willReturn($expectedItem['is_saleable']);
131-
$itemWithoutProductMock->expects($this->once())->method('hasData')->with('product')->willReturn(false);
132-
$this->assertEquals(['items' => [$expectedItem]], $this->section->getSectionData());
172+
->willReturnMap([
173+
[$productIdVisible, $websiteId, $stockItemMock],
174+
[$productIdNotVisible, $websiteId, $stockItemMock],
175+
]);
176+
$stockItemMock->expects($this->exactly(2))->method('getIsInStock')->willReturn($expectedItem1['is_saleable']);
177+
$this->assertEquals(['items' => [$expectedItem1, $expectedItem2]], $this->section->getSectionData());
133178
}
134179

135180
private function getLastOrderMock()
@@ -160,4 +205,34 @@ private function getLastOrderMock()
160205
->willReturnSelf();
161206
return $this->orderMock;
162207
}
208+
209+
public function testGetSectionDataWithNotExistingProduct()
210+
{
211+
$storeId = 1;
212+
$websiteId = 4;
213+
$productId = 1;
214+
$exception = new \Magento\Framework\Exception\NoSuchEntityException(__("Product doesn't exist"));
215+
$orderItemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
216+
->disableOriginalConstructor()
217+
->setMethods(['getProductId'])
218+
->getMock();
219+
$storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass();
220+
221+
$this->getLastOrderMock();
222+
$this->storeManagerMock->expects($this->exactly(2))->method('getStore')->willReturn($storeMock);
223+
$storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId);
224+
$storeMock->expects($this->once())->method('getId')->willReturn($storeId);
225+
$this->orderMock->expects($this->once())
226+
->method('getParentItemsRandomCollection')
227+
->with(\Magento\Sales\CustomerData\LastOrderedItems::SIDEBAR_ORDER_LIMIT)
228+
->willReturn([$orderItemMock]);
229+
$orderItemMock->expects($this->once())->method('getProductId')->willReturn($productId);
230+
$this->productRepositoryMock->expects($this->once())
231+
->method('getById')
232+
->with($productId, false, $storeId)
233+
->willThrowException($exception);
234+
$this->loggerMock->expects($this->once())->method('critical')->with($exception);
235+
236+
$this->assertEquals(['items' => []], $this->section->getSectionData());
237+
}
163238
}

app/code/Magento/Store/App/Action/Plugin/Context.php

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
namespace Magento\Store\App\Action\Plugin;
88

99
use Magento\Framework\App\Http\Context as HttpContext;
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Magento\Framework\Exception\NotFoundException;
1012
use Magento\Framework\Phrase;
13+
use Magento\Store\Api\Data\StoreInterface;
1114
use Magento\Store\Api\StoreCookieManagerInterface;
1215
use Magento\Store\Api\StoreResolverInterface;
1316
use Magento\Store\Model\StoreManagerInterface;
@@ -58,42 +61,102 @@ public function __construct(
5861
}
5962

6063
/**
61-
* Set store and currency to http context
64+
* Set store and currency to http context.
6265
*
6366
* @param AbstractAction $subject
6467
* @param RequestInterface $request
6568
* @return void
6669
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
6770
*/
68-
public function beforeDispatch(AbstractAction $subject, RequestInterface $request)
69-
{
70-
/** @var \Magento\Store\Model\Store $defaultStore */
71-
$defaultStore = $this->storeManager->getWebsite()->getDefaultStore();
71+
public function beforeDispatch(
72+
AbstractAction $subject,
73+
RequestInterface $request
74+
) {
75+
if ($this->isAlreadySet()) {
76+
//If required store related value were already set for
77+
//HTTP processors then just continuing as we were.
78+
return;
79+
}
7280

81+
/** @var string|array|null $storeCode */
7382
$storeCode = $request->getParam(
7483
StoreResolverInterface::PARAM_NAME,
7584
$this->storeCookieManager->getStoreCodeFromCookie()
7685
);
77-
7886
if (is_array($storeCode)) {
7987
if (!isset($storeCode['_data']['code'])) {
80-
throw new \InvalidArgumentException(new Phrase('Invalid store parameter.'));
88+
$this->processInvalidStoreRequested();
8189
}
8290
$storeCode = $storeCode['_data']['code'];
8391
}
84-
/** @var \Magento\Store\Model\Store $currentStore */
85-
$currentStore = $storeCode ? $this->storeManager->getStore($storeCode) : $defaultStore;
92+
if ($storeCode === '') {
93+
//Empty code - is an invalid code and it was given explicitly
94+
//(the value would be null if the code wasn't found).
95+
$this->processInvalidStoreRequested();
96+
}
97+
try {
98+
$currentStore = $this->storeManager->getStore($storeCode);
99+
} catch (NoSuchEntityException $exception) {
100+
$this->processInvalidStoreRequested($exception);
101+
}
102+
103+
$this->updateContext($currentStore);
104+
}
105+
106+
/**
107+
* Take action in case of invalid store requested.
108+
*
109+
* @param \Throwable|null $previousException
110+
* @return void
111+
* @throws NotFoundException
112+
*/
113+
private function processInvalidStoreRequested(
114+
\Throwable $previousException = null
115+
) {
116+
$store = $this->storeManager->getStore();
117+
$this->updateContext($store);
86118

119+
throw new NotFoundException(
120+
$previousException
121+
? __($previousException->getMessage())
122+
: __('Invalid store requested.'),
123+
$previousException
124+
);
125+
}
126+
127+
/**
128+
* Update context accordingly to the store found.
129+
*
130+
* @param StoreInterface $store
131+
* @return void
132+
*/
133+
private function updateContext(StoreInterface $store)
134+
{
87135
$this->httpContext->setValue(
88136
StoreManagerInterface::CONTEXT_STORE,
89-
$currentStore->getCode(),
137+
$store->getCode(),
90138
$this->storeManager->getDefaultStoreView()->getCode()
91139
);
92140

141+
/** @var StoreInterface $defaultStore */
142+
$defaultStore = $this->storeManager->getWebsite()->getDefaultStore();
93143
$this->httpContext->setValue(
94144
HttpContext::CONTEXT_CURRENCY,
95-
$this->session->getCurrencyCode() ?: $currentStore->getDefaultCurrencyCode(),
145+
$this->session->getCurrencyCode()
146+
?: $store->getDefaultCurrencyCode(),
96147
$defaultStore->getDefaultCurrencyCode()
97148
);
98149
}
150+
151+
/**
152+
* Check if there is a need to find the current store.
153+
*
154+
* @return bool
155+
*/
156+
private function isAlreadySet(): bool
157+
{
158+
$storeKey = StoreManagerInterface::CONTEXT_STORE;
159+
160+
return $this->httpContext->getValue($storeKey) !== null;
161+
}
99162
}

0 commit comments

Comments
 (0)