Skip to content

Commit 8f20696

Browse files
Merge remote-tracking branch 'origin/MC-34100' into 2.4-develop-pr35
2 parents 2a0942a + 8a1a45b commit 8f20696

File tree

8 files changed

+218
-1
lines changed

8 files changed

+218
-1
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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\Catalog\Model;
9+
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Framework\App\RequestInterface;
12+
use Magento\Framework\Exception\NoSuchEntityException;
13+
use Magento\PageCache\Model\Spi\PageCacheTagsPreprocessorInterface;
14+
use Magento\Store\Model\StoreManagerInterface;
15+
16+
/**
17+
* Add product identities to "noroute" page
18+
*
19+
* Ensure that "noroute" page has necessary product tags
20+
* so it can be invalidated once the product becomes visible again
21+
*/
22+
class ProductNotFoundPageCacheTags implements PageCacheTagsPreprocessorInterface
23+
{
24+
private const NOROUTE_ACTION_NAME = 'cms_noroute_index';
25+
/**
26+
* @var ProductRepositoryInterface
27+
*/
28+
private $productRepository;
29+
/**
30+
* @var StoreManagerInterface
31+
*/
32+
private $storeManager;
33+
/**
34+
* @var RequestInterface
35+
*/
36+
private $request;
37+
38+
/**
39+
* @param RequestInterface $request
40+
* @param ProductRepositoryInterface $productRepository
41+
* @param StoreManagerInterface $storeManager
42+
*/
43+
public function __construct(
44+
RequestInterface $request,
45+
ProductRepositoryInterface $productRepository,
46+
StoreManagerInterface $storeManager
47+
) {
48+
$this->productRepository = $productRepository;
49+
$this->storeManager = $storeManager;
50+
$this->request = $request;
51+
}
52+
53+
/**
54+
* @inheritDoc
55+
*/
56+
public function process(array $tags): array
57+
{
58+
if ($this->request->getFullActionName() === self::NOROUTE_ACTION_NAME) {
59+
try {
60+
$productId = (int) $this->request->getParam('id');
61+
$product = $this->productRepository->getById(
62+
$productId,
63+
false,
64+
$this->storeManager->getStore()->getId()
65+
);
66+
} catch (NoSuchEntityException $e) {
67+
$product = null;
68+
}
69+
if ($product) {
70+
$tags = array_merge($tags, $product->getIdentities());
71+
}
72+
}
73+
return $tags;
74+
}
75+
}

app/code/Magento/Catalog/etc/frontend/di.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,13 @@
115115
<plugin name="catalog_app_action_dispatch_controller_context_plugin"
116116
type="Magento\Catalog\Plugin\Framework\App\Action\ContextPlugin" />
117117
</type>
118+
<type name="\Magento\PageCache\Model\PageCacheTagsPreprocessorComposite">
119+
<arguments>
120+
<argument name="preprocessors" xsi:type="array">
121+
<item name="catalog_product_view" xsi:type="array">
122+
<item name="product_not_found" xsi:type="object">Magento\Catalog\Model\ProductNotFoundPageCacheTags</item>
123+
</item>
124+
</argument>
125+
</arguments>
126+
</type>
118127
</config>

app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
namespace Magento\PageCache\Model\Layout;
99

1010
use Magento\Framework\App\MaintenanceMode;
11+
use Magento\Framework\App\ObjectManager;
1112
use Magento\Framework\App\ResponseInterface;
1213
use Magento\Framework\DataObject\IdentityInterface;
1314
use Magento\Framework\View\Layout;
1415
use Magento\PageCache\Model\Config;
16+
use Magento\PageCache\Model\Spi\PageCacheTagsPreprocessorInterface;
1517

1618
/**
1719
* Append cacheable pages response headers.
@@ -28,6 +30,11 @@ class LayoutPlugin
2830
*/
2931
private $response;
3032

33+
/**
34+
* @var PageCacheTagsPreprocessorInterface
35+
*/
36+
private $pageCacheTagsPreprocessor;
37+
3138
/**
3239
* @var MaintenanceMode
3340
*/
@@ -37,15 +44,19 @@ class LayoutPlugin
3744
* @param ResponseInterface $response
3845
* @param Config $config
3946
* @param MaintenanceMode $maintenanceMode
47+
* @param PageCacheTagsPreprocessorInterface|null $pageCacheTagsPreprocessor
4048
*/
4149
public function __construct(
4250
ResponseInterface $response,
4351
Config $config,
44-
MaintenanceMode $maintenanceMode
52+
MaintenanceMode $maintenanceMode,
53+
?PageCacheTagsPreprocessorInterface $pageCacheTagsPreprocessor = null
4554
) {
4655
$this->response = $response;
4756
$this->config = $config;
4857
$this->maintenanceMode = $maintenanceMode;
58+
$this->pageCacheTagsPreprocessor = $pageCacheTagsPreprocessor
59+
?? ObjectManager::getInstance()->get(PageCacheTagsPreprocessorInterface::class);
4960
}
5061

5162
/**
@@ -85,6 +96,7 @@ public function afterGetOutput(Layout $subject, $result)
8596
}
8697
}
8798
$tags = array_unique(array_merge(...$tags));
99+
$tags = $this->pageCacheTagsPreprocessor->process($tags);
88100
$this->response->setHeader('X-Magento-Tags', implode(',', $tags));
89101
}
90102

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\PageCache\Model;
9+
10+
use InvalidArgumentException;
11+
use Magento\Framework\App\RequestInterface;
12+
use Magento\PageCache\Model\Spi\PageCacheTagsPreprocessorInterface;
13+
14+
/**
15+
* Composite page cache preprocessors
16+
*/
17+
class PageCacheTagsPreprocessorComposite implements PageCacheTagsPreprocessorInterface
18+
{
19+
/**
20+
* @var PageCacheTagsPreprocessorInterface[][]
21+
*/
22+
private $preprocessors;
23+
/**
24+
* @var RequestInterface
25+
*/
26+
private $request;
27+
28+
/**
29+
* @param RequestInterface $request
30+
* @param PageCacheTagsPreprocessorInterface[][] $preprocessors
31+
*/
32+
public function __construct(
33+
RequestInterface $request,
34+
array $preprocessors = []
35+
) {
36+
foreach ($preprocessors as $group) {
37+
foreach ($group as $preprocessor) {
38+
if (!$preprocessor instanceof PageCacheTagsPreprocessorInterface) {
39+
throw new InvalidArgumentException(
40+
sprintf(
41+
'Instance of %s is expected, got %s instead.',
42+
PageCacheTagsPreprocessorInterface::class,
43+
get_class($preprocessor)
44+
)
45+
);
46+
}
47+
}
48+
}
49+
$this->preprocessors = $preprocessors;
50+
$this->request = $request;
51+
}
52+
53+
/**
54+
* @inheritDoc
55+
*/
56+
public function process(array $tags): array
57+
{
58+
$forwardInfo = $this->request->getBeforeForwardInfo();
59+
$actionName = $forwardInfo
60+
? implode('_', [$forwardInfo['route_name'], $forwardInfo['controller_name'], $forwardInfo['action_name']])
61+
: $this->request->getFullActionName();
62+
if (isset($this->preprocessors[$actionName])) {
63+
foreach ($this->preprocessors[$actionName] as $preprocessor) {
64+
$tags = $preprocessor->process($tags);
65+
}
66+
}
67+
return $tags;
68+
}
69+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\PageCache\Model\Spi;
9+
10+
/**
11+
* Interface for page tags preprocessors
12+
*/
13+
interface PageCacheTagsPreprocessorInterface
14+
{
15+
/**
16+
* Change page tags and returned the modified tags
17+
*
18+
* @param array $tags
19+
* @return array
20+
*/
21+
public function process(array $tags): array;
22+
}

app/code/Magento/PageCache/Test/Unit/Model/Layout/LayoutPluginTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Magento\Framework\View\Layout;
1616
use Magento\PageCache\Model\Config;
1717
use Magento\PageCache\Model\Layout\LayoutPlugin;
18+
use Magento\PageCache\Model\Spi\PageCacheTagsPreprocessorInterface;
1819
use Magento\PageCache\Test\Unit\Block\Controller\StubBlock;
1920
use PHPUnit\Framework\MockObject\MockObject;
2021
use PHPUnit\Framework\TestCase;
@@ -58,13 +59,16 @@ protected function setUp(): void
5859
$this->responseMock = $this->createMock(Http::class);
5960
$this->configMock = $this->createMock(Config::class);
6061
$this->maintenanceModeMock = $this->createMock(MaintenanceMode::class);
62+
$preprocessor = $this->createMock(PageCacheTagsPreprocessorInterface::class);
63+
$preprocessor->method('process')->willReturnArgument(0);
6164

6265
$this->model = (new ObjectManagerHelper($this))->getObject(
6366
LayoutPlugin::class,
6467
[
6568
'response' => $this->responseMock,
6669
'config' => $this->configMock,
6770
'maintenanceMode' => $this->maintenanceModeMock,
71+
'pageCacheTagsPreprocessor' => $preprocessor
6872
]
6973
);
7074
}

app/code/Magento/PageCache/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@
4949
</type>
5050
<preference for="Magento\PageCache\Model\VclGeneratorInterface" type="Magento\PageCache\Model\Varnish\VclGenerator"/>
5151
<preference for="Magento\PageCache\Model\VclTemplateLocatorInterface" type="Magento\PageCache\Model\Varnish\VclTemplateLocator"/>
52+
<preference for="Magento\PageCache\Model\Spi\PageCacheTagsPreprocessorInterface" type="Magento\PageCache\Model\PageCacheTagsPreprocessorComposite"/>
5253
</config>

dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/ViewTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Catalog\Model\Product;
1414
use Magento\Catalog\Model\Product\Visibility;
1515
use Magento\Eav\Model\Entity\Type;
16+
use Magento\Framework\App\Cache\Manager;
1617
use Magento\Framework\App\Http;
1718
use Magento\Framework\Registry;
1819
use Magento\Store\Model\StoreManagerInterface;
@@ -268,6 +269,30 @@ public function testProductWithoutWebsite(): void
268269
$this->assert404NotFound();
269270
}
270271

272+
/**
273+
* Test that 404 page has product tag if product is not visible
274+
*
275+
* @magentoDataFixture Magento/Quote/_files/is_not_salable_product.php
276+
* @magentoCache full_page enabled
277+
* @return void
278+
*/
279+
public function test404NotFoundPageCacheTags(): void
280+
{
281+
$cache = $this->_objectManager->get(Manager::class);
282+
$cache->clean(['full_page']);
283+
$product = $this->productRepository->get('simple-99');
284+
$this->dispatch(sprintf('catalog/product/view/id/%s/', $product->getId()));
285+
$this->assert404NotFound();
286+
$pTag = Product::CACHE_TAG . '_' . $product->getId();
287+
$hTags = $this->getResponse()->getHeader('X-Magento-Tags');
288+
$tags = $hTags && $hTags->getFieldValue() ? explode(',', $hTags->getFieldValue()) : [];
289+
$this->assertContains(
290+
$pTag,
291+
$tags,
292+
"Failed asserting that X-Magento-Tags: {$hTags->getFieldValue()} contains \"$pTag\""
293+
);
294+
}
295+
271296
/**
272297
* @param string|ProductInterface $product
273298
* @param array $data

0 commit comments

Comments
 (0)