Skip to content

Commit 4a7ee52

Browse files
MarianaMariana
authored andcommitted
Merge branch 'MAGETWO-94068' into mpi-forwardport-0309
2 parents 980e971 + c8cc913 commit 4a7ee52

File tree

6 files changed

+285
-1
lines changed

6 files changed

+285
-1
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Observer;
9+
10+
use Magento\Catalog\Model\Indexer\Category\Product\Processor;
11+
use Magento\Framework\Event\Observer;
12+
use Magento\Framework\Event\ObserverInterface;
13+
14+
/**
15+
* Checks if a category has changed products and depends on indexer configuration
16+
* marks `Category Products` indexer as invalid or reindexes affected products.
17+
*/
18+
class CategoryProductIndexer implements ObserverInterface
19+
{
20+
/**
21+
* @var Processor
22+
*/
23+
private $processor;
24+
25+
/**
26+
* @param Processor $processor
27+
*/
28+
public function __construct(Processor $processor)
29+
{
30+
$this->processor = $processor;
31+
}
32+
33+
/**
34+
* @inheritdoc
35+
*/
36+
public function execute(Observer $observer): void
37+
{
38+
$productIds = $observer->getEvent()->getProductIds();
39+
if (!empty($productIds) && $this->processor->isIndexerScheduled()) {
40+
$this->processor->markIndexerAsInvalid();
41+
}
42+
}
43+
}

app/code/Magento/Catalog/etc/adminhtml/events.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@
99
<event name="cms_wysiwyg_images_static_urls_allowed">
1010
<observer name="catalog_wysiwyg" instance="Magento\Catalog\Observer\CatalogCheckIsUsingStaticUrlsAllowedObserver" />
1111
</event>
12+
<event name="catalog_category_change_products">
13+
<observer name="category_product_indexer" instance="Magento\Catalog\Observer\CategoryProductIndexer"/>
14+
</event>
1215
</config>

app/code/Magento/Elasticsearch/Model/Config.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Config implements ClientOptionsInterface
2525
*/
2626
const ENGINE_NAME = 'elasticsearch';
2727

28+
private const ENGINE_NAME_5 = 'elasticsearch5';
29+
2830
/**
2931
* Elasticsearch Entity type
3032
*/
@@ -135,7 +137,7 @@ public function getSearchConfigData($field, $storeId = null)
135137
*/
136138
public function isElasticsearchEnabled()
137139
{
138-
return $this->engineResolver->getCurrentSearchEngine() === self::ENGINE_NAME;
140+
return in_array($this->engineResolver->getCurrentSearchEngine(), [self::ENGINE_NAME, self::ENGINE_NAME_5]);
139141
}
140142

141143
/**
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Elasticsearch\Observer;
9+
10+
use Magento\CatalogSearch\Model\Indexer\Fulltext\Processor;
11+
use Magento\Elasticsearch\Model\Config;
12+
use Magento\Framework\Event\Observer;
13+
use Magento\Framework\Event\ObserverInterface;
14+
15+
/**
16+
* Checks if a category has changed products and depends on indexer configuration
17+
* marks `Catalog Search` indexer as invalid or reindexes affected products.
18+
*/
19+
class CategoryProductIndexer implements ObserverInterface
20+
{
21+
/**
22+
* @var Config
23+
*/
24+
private $config;
25+
26+
/**
27+
* @var Processor
28+
*/
29+
private $processor;
30+
31+
/**
32+
* @param Config $config
33+
* @param Processor $processor
34+
*/
35+
public function __construct(Config $config, Processor $processor)
36+
{
37+
$this->processor = $processor;
38+
$this->config = $config;
39+
}
40+
41+
/**
42+
* @inheritdoc
43+
*/
44+
public function execute(Observer $observer): void
45+
{
46+
if (!$this->config->isElasticsearchEnabled()) {
47+
return;
48+
}
49+
50+
$productIds = $observer->getEvent()->getProductIds();
51+
if (!empty($productIds) && $this->processor->isIndexerScheduled()) {
52+
$this->processor->markIndexerAsInvalid();
53+
}
54+
}
55+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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:Event/etc/events.xsd">
9+
<event name="catalog_category_change_products">
10+
<observer name="category_product_elasticsearch_indexer" instance="Magento\Elasticsearch\Observer\CategoryProductIndexer"/>
11+
</event>
12+
</config>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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\Elasticsearch\Controller\Adminhtml\Category;
9+
10+
use Magento\Catalog\Api\CategoryListInterface;
11+
use Magento\Catalog\Api\Data\CategoryInterface;
12+
use Magento\Catalog\Api\Data\ProductInterface;
13+
use Magento\Catalog\Api\ProductRepositoryInterface;
14+
use Magento\Catalog\Model\Indexer\Category\Product as CategoryIndexer;
15+
use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer;
16+
use Magento\Elasticsearch\Model\Config;
17+
use Magento\Framework\Api\SearchCriteriaBuilder;
18+
use Magento\Framework\Indexer\IndexerInterface;
19+
use Magento\Framework\Indexer\IndexerRegistry;
20+
use Magento\Framework\Message\MessageInterface;
21+
use Magento\TestFramework\TestCase\AbstractBackendController;
22+
23+
/**
24+
* @magentoAppArea adminhtml
25+
* @magentoDbIsolation disabled
26+
*/
27+
class SaveTest extends AbstractBackendController
28+
{
29+
private $indexerSchedule = [];
30+
31+
/**
32+
* @inheritdoc
33+
*/
34+
protected function setUp()
35+
{
36+
parent::setUp();
37+
38+
$config = $this->getMockBuilder(Config::class)
39+
->disableOriginalConstructor()
40+
->getMock();
41+
$config->method('isElasticsearchEnabled')
42+
->willReturn(true);
43+
$this->_objectManager->addSharedInstance($config, Config::class);
44+
45+
$this->changeIndexerSchedule(FulltextIndexer::INDEXER_ID, true);
46+
$this->changeIndexerSchedule(CategoryIndexer::INDEXER_ID, true);
47+
}
48+
49+
/**
50+
* @inheritdoc
51+
*/
52+
protected function tearDown()
53+
{
54+
$this->_objectManager->removeSharedInstance(Config::class);
55+
$this->changeIndexerSchedule(FulltextIndexer::INDEXER_ID, $this->indexerSchedule[FulltextIndexer::INDEXER_ID]);
56+
$this->changeIndexerSchedule(CategoryIndexer::INDEXER_ID, $this->indexerSchedule[CategoryIndexer::INDEXER_ID]);
57+
58+
parent::tearDown();
59+
}
60+
61+
/**
62+
* Checks a case when indexers are invalidated if products for category were changed.
63+
*
64+
* @magentoDataFixture Magento/Catalog/_files/category_product.php
65+
* @magentoDataFixture Magento/Catalog/_files/multiple_products.php
66+
*/
67+
public function testExecute()
68+
{
69+
$fulltextIndexer = $this->getIndexer(FulltextIndexer::INDEXER_ID);
70+
self::assertTrue($fulltextIndexer->isValid(), 'Fulltext indexer should be valid.');
71+
$categoryIndexer = $this->getIndexer(CategoryIndexer::INDEXER_ID);
72+
self::assertTrue($categoryIndexer->isValid(), 'Category indexer should be valid.');
73+
74+
$category = $this->getCategory('Category 1');
75+
$productIdList = $this->getProductIdList(['simple1', 'simple2', 'simple3']);
76+
$inputData = [
77+
'category_products' => json_encode(array_fill_keys($productIdList, [0, 1, 2])),
78+
'entity_id' => $category->getId(),
79+
'default_sort_by' => 'position'
80+
];
81+
82+
$this->getRequest()->setPostValue($inputData);
83+
$this->dispatch('backend/catalog/category/save');
84+
$this->assertSessionMessages(
85+
self::equalTo(['You saved the category.']),
86+
MessageInterface::TYPE_SUCCESS
87+
);
88+
89+
$fulltextIndexer = $this->getIndexer(FulltextIndexer::INDEXER_ID);
90+
self::assertTrue($fulltextIndexer->isInvalid(), 'Fulltext indexer should be invalidated.');
91+
$categoryIndexer = $this->getIndexer(CategoryIndexer::INDEXER_ID);
92+
self::assertTrue($categoryIndexer->isInvalid(), 'Category indexer should be invalidated.');
93+
}
94+
95+
/**
96+
* Gets indexer from registry by ID.
97+
*
98+
* @param string $indexerId
99+
* @return IndexerInterface
100+
*/
101+
private function getIndexer(string $indexerId): IndexerInterface
102+
{
103+
/** @var IndexerRegistry $indexerRegistry */
104+
$indexerRegistry = $this->_objectManager->get(IndexerRegistry::class);
105+
return $indexerRegistry->get($indexerId);
106+
}
107+
108+
/**
109+
* Changes the scheduled state of indexer.
110+
*
111+
* @param string $indexerId
112+
* @param bool $isScheduled
113+
* @return void
114+
*/
115+
private function changeIndexerSchedule(string $indexerId, bool $isScheduled): void
116+
{
117+
$indexer = $this->getIndexer($indexerId);
118+
if (!isset($this->indexerSchedule[$indexerId])) {
119+
$this->indexerSchedule[$indexerId] = $indexer->isScheduled();
120+
$indexer->reindexAll();
121+
}
122+
$indexer->setScheduled($isScheduled);
123+
}
124+
125+
/**
126+
* Gets category by name.
127+
*
128+
* @param string $name
129+
* @return CategoryInterface
130+
*/
131+
private function getCategory(string $name): CategoryInterface
132+
{
133+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
134+
$searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class);
135+
$searchCriteria = $searchCriteriaBuilder->addFilter('name', $name)
136+
->create();
137+
/** @var CategoryListInterface $repository */
138+
$repository = $this->_objectManager->get(CategoryListInterface::class);
139+
$items = $repository->getList($searchCriteria)
140+
->getItems();
141+
142+
return array_pop($items);
143+
}
144+
145+
/**
146+
* Gets list of product ID by SKU.
147+
*
148+
* @param array $skuList
149+
* @return array
150+
*/
151+
private function getProductIdList(array $skuList): array
152+
{
153+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
154+
$searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class);
155+
$searchCriteria = $searchCriteriaBuilder->addFilter('sku', $skuList, 'in')
156+
->create();
157+
158+
/** @var ProductRepositoryInterface $repository */
159+
$repository = $this->_objectManager->get(ProductRepositoryInterface::class);
160+
$items = $repository->getList($searchCriteria)
161+
->getItems();
162+
163+
$idList = array_map(function (ProductInterface $item) {
164+
return $item->getId();
165+
}, $items);
166+
167+
return $idList;
168+
}
169+
}

0 commit comments

Comments
 (0)