Skip to content

Commit fd90f65

Browse files
authored
ENGCOM-3566: Include custom source model options in attribute index - Up port #16727 #19176
2 parents 9c531fd + 6f1dcdb commit fd90f65

File tree

7 files changed

+283
-1
lines changed

7 files changed

+283
-1
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
99
use Magento\Catalog\Api\Data\ProductInterface;
10+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
1011

1112
/**
1213
* Catalog Product Eav Select and Multiply Select Attributes Indexer resource model
@@ -24,6 +25,16 @@ class Source extends AbstractEav
2425
*/
2526
protected $_resourceHelper;
2627

28+
/**
29+
* @var \Magento\Eav\Api\AttributeRepositoryInterface
30+
*/
31+
private $attributeRepository;
32+
33+
/**
34+
* @var \Magento\Framework\Api\SearchCriteriaBuilder
35+
*/
36+
private $criteriaBuilder;
37+
2738
/**
2839
* Construct
2940
*
@@ -33,14 +44,18 @@ class Source extends AbstractEav
3344
* @param \Magento\Framework\Event\ManagerInterface $eventManager
3445
* @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
3546
* @param null|string $connectionName
47+
* @param \Magento\Eav\Api\AttributeRepositoryInterface|null $attributeRepository
48+
* @param \Magento\Framework\Api\SearchCriteriaBuilder|null $criteriaBuilder
3649
*/
3750
public function __construct(
3851
\Magento\Framework\Model\ResourceModel\Db\Context $context,
3952
\Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy,
4053
\Magento\Eav\Model\Config $eavConfig,
4154
\Magento\Framework\Event\ManagerInterface $eventManager,
4255
\Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
43-
$connectionName = null
56+
$connectionName = null,
57+
\Magento\Eav\Api\AttributeRepositoryInterface $attributeRepository = null,
58+
\Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder = null
4459
) {
4560
parent::__construct(
4661
$context,
@@ -50,6 +65,12 @@ public function __construct(
5065
$connectionName
5166
);
5267
$this->_resourceHelper = $resourceHelper;
68+
$this->attributeRepository = $attributeRepository
69+
?: \Magento\Framework\App\ObjectManager::getInstance()
70+
->get(\Magento\Eav\Api\AttributeRepositoryInterface::class);
71+
$this->criteriaBuilder = $criteriaBuilder
72+
?: \Magento\Framework\App\ObjectManager::getInstance()
73+
->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
5374
}
5475

5576
/**
@@ -234,6 +255,10 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
234255
$options[$row['attribute_id']][$row['option_id']] = true;
235256
}
236257

258+
// Retrieve any custom source model options
259+
$sourceModelOptions = $this->getMultiSelectAttributeWithSourceModels($attrIds);
260+
$options = array_replace_recursive($options, $sourceModelOptions);
261+
237262
// prepare get multiselect values query
238263
$productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value');
239264
$select = $connection->select()->from(
@@ -297,6 +322,39 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
297322
return $this;
298323
}
299324

325+
/**
326+
* Get options for multiselect attributes using custom source models
327+
* Based on @maderlock's fix from:
328+
* https://github.com/magento/magento2/issues/417#issuecomment-265146285
329+
*
330+
* @param array $attrIds
331+
*
332+
* @return array
333+
*/
334+
private function getMultiSelectAttributeWithSourceModels($attrIds)
335+
{
336+
// Add options from custom source models
337+
$this->criteriaBuilder
338+
->addFilter('attribute_id', $attrIds, 'in')
339+
->addFilter('source_model', true, 'notnull');
340+
$criteria = $this->criteriaBuilder->create();
341+
$attributes = $this->attributeRepository->getList(
342+
ProductAttributeInterface::ENTITY_TYPE_CODE,
343+
$criteria
344+
)->getItems();
345+
346+
$options = [];
347+
foreach ($attributes as $attribute) {
348+
$sourceModelOptions = $attribute->getOptions();
349+
// Add options to list used below
350+
foreach ($sourceModelOptions as $option) {
351+
$options[$attribute->getAttributeId()][$option->getValue()] = true;
352+
}
353+
}
354+
355+
return $options;
356+
}
357+
300358
/**
301359
* Save a data to temporary source index table
302360
*

dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Magento\Catalog\Api\ProductRepositoryInterface;
99
use Magento\Catalog\Model\Product\Attribute\Source\Status;
1010
use Magento\TestFramework\Helper\Bootstrap;
11+
use Magento\Catalog\_files\MultiselectSourceMock;
1112

1213
/**
1314
* Class SourceTest
@@ -157,4 +158,50 @@ public function testReindexMultiselectAttribute()
157158
$result = $connection->fetchAll($select);
158159
$this->assertCount(3, $result);
159160
}
161+
162+
/**
163+
* @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute_with_source_model.php
164+
* @magentoDbIsolation disabled
165+
*/
166+
public function testReindexMultiselectAttributeWithSourceModel()
167+
{
168+
$objectManager = Bootstrap::getObjectManager();
169+
170+
/** @var ProductRepositoryInterface $productRepository */
171+
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
172+
173+
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attr **/
174+
$attr = $objectManager->get(\Magento\Eav\Model\Config::class)
175+
->getAttribute('catalog_product', 'multiselect_attr_with_source');
176+
177+
/** @var $sourceModel MultiselectSourceMock */
178+
$sourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
179+
MultiselectSourceMock::class
180+
);
181+
$options = $sourceModel->getAllOptions();
182+
$product1Id = $options[0]['value'] * 10;
183+
$product2Id = $options[1]['value'] * 10;
184+
185+
/** @var \Magento\Catalog\Model\Product $product1 **/
186+
$product1 = $productRepository->getById($product1Id);
187+
$product1->setSpecialFromDate(date('Y-m-d H:i:s'));
188+
$product1->setNewsFromDate(date('Y-m-d H:i:s'));
189+
$productRepository->save($product1);
190+
191+
/** @var \Magento\Catalog\Model\Product $product2 **/
192+
$product2 = $productRepository->getById($product2Id);
193+
$product1->setSpecialFromDate(date('Y-m-d H:i:s'));
194+
$product1->setNewsFromDate(date('Y-m-d H:i:s'));
195+
$productRepository->save($product2);
196+
197+
$this->_eavIndexerProcessor->reindexAll();
198+
$connection = $this->productResource->getConnection();
199+
$select = $connection->select()
200+
->from($this->productResource->getTable('catalog_product_index_eav'))
201+
->where('entity_id in (?)', [$product1Id, $product2Id])
202+
->where('attribute_id = ?', $attr->getId());
203+
204+
$result = $connection->fetchAll($select);
205+
$this->assertCount(3, $result);
206+
}
160207
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Catalog\_files;
7+
8+
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
9+
10+
/**
11+
* Creates mock source for multiselect attributes
12+
*/
13+
class MultiselectSourceMock extends AbstractSource
14+
{
15+
16+
public function getAllOptions()
17+
{
18+
return [
19+
['value' => 1, 'label' => 'Option 1'],
20+
['value' => 2, 'label' => 'Option 2'],
21+
['value' => 3, 'label' => 'Option 3'],
22+
['value' => 4, 'label' => 'Option 4 "!@#$%^&*'],
23+
];
24+
}
25+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
/* Create attribute */
7+
/** @var $installer \Magento\Catalog\Setup\CategorySetup */
8+
$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
9+
\Magento\Catalog\Setup\CategorySetup::class
10+
);
11+
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
12+
$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
13+
\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class
14+
);
15+
$entityType = $installer->getEntityTypeId('catalog_product');
16+
if (!$attribute->loadByCode($entityType, 'multiselect_attr_with_source')->getAttributeId()) {
17+
$attribute->setData(
18+
[
19+
'attribute_code' => 'multiselect_attr_with_source',
20+
'entity_type_id' => $entityType,
21+
'is_global' => 1,
22+
'is_user_defined' => 1,
23+
'frontend_input' => 'multiselect',
24+
'is_unique' => 0,
25+
'is_required' => 0,
26+
'is_searchable' => 0,
27+
'is_visible_in_advanced_search' => 0,
28+
'is_comparable' => 0,
29+
'is_filterable' => 1,
30+
'is_filterable_in_search' => 0,
31+
'is_used_for_promo_rules' => 0,
32+
'is_html_allowed_on_front' => 1,
33+
'is_visible_on_front' => 0,
34+
'used_in_product_listing' => 0,
35+
'used_for_sort_by' => 0,
36+
'frontend_label' => ['Multiselect Attribute with Source Model'],
37+
'backend_type' => 'varchar',
38+
'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class,
39+
'source_model' => \Magento\Catalog\_files\MultiselectSourceMock::class
40+
]
41+
);
42+
$attribute->save();
43+
44+
/* Assign attribute to attribute set */
45+
$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId());
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
/* Delete attribute with multiselect_attribute code */
7+
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
8+
$registry->unregister('isSecureArea');
9+
$registry->register('isSecureArea', true);
10+
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
11+
$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
12+
'Magento\Catalog\Model\ResourceModel\Eav\Attribute'
13+
);
14+
$attribute->load('multiselect_attr_with_source', 'attribute_code');
15+
$attribute->delete();
16+
17+
$registry->unregister('isSecureArea');
18+
$registry->register('isSecureArea', false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
use Magento\Catalog\_files\MultiselectSourceMock;
8+
9+
/**
10+
* Create multiselect attribute
11+
*/
12+
require __DIR__ . '/multiselect_attribute_with_source_model.php';
13+
require __DIR__ . '/../../Checkout/_files/ValidatorFileMock.php';
14+
15+
/** Create product with options and multiselect attribute */
16+
17+
/** @var $installer \Magento\Catalog\Setup\CategorySetup */
18+
$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
19+
\Magento\Catalog\Setup\CategorySetup::class
20+
);
21+
22+
/** @var $sourceModel MultiselectSourceMock */
23+
$sourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
24+
MultiselectSourceMock::class
25+
);
26+
$options = $sourceModel->getAllOptions();
27+
28+
/** @var $product \Magento\Catalog\Model\Product */
29+
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class);
30+
$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
31+
->setId($options[0]['value'] * 10)
32+
->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
33+
->setWebsiteIds([1])
34+
->setName('With Multiselect Source Model 1')
35+
->setSku('simple_mssm_1')
36+
->setPrice(10)
37+
->setDescription('Hello " &amp;" Bring the water bottle when you can!')
38+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
39+
->setMultiselectAttrWithSource([$options[0]['value']])
40+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
41+
->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
42+
->save();
43+
44+
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class);
45+
$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
46+
->setId($options[1]['value'] * 10)
47+
->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
48+
->setWebsiteIds([1])
49+
->setName('With Multiselect Source Model 2')
50+
->setSku('simple_mssm_2')
51+
->setPrice(10)
52+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
53+
->setMultiselectAttrWithSource([$options[1]['value'], $options[2]['value'], $options[3]['value']])
54+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
55+
->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
56+
->save();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
require __DIR__ . '/multiselect_attribute_with_source_model_rollback.php';
8+
9+
use Magento\Framework\Indexer\IndexerRegistry;
10+
11+
/**
12+
* Remove all products as strategy of isolation process
13+
*/
14+
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
15+
$registry->unregister('isSecureArea');
16+
$registry->register('isSecureArea', true);
17+
18+
/** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product */
19+
$productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
20+
->create('Magento\Catalog\Model\Product')
21+
->getCollection();
22+
23+
foreach ($productCollection as $product) {
24+
$product->delete();
25+
}
26+
27+
$registry->unregister('isSecureArea');
28+
$registry->register('isSecureArea', false);
29+
30+
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(IndexerRegistry::class)
31+
->get(Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID)
32+
->reindexAll();

0 commit comments

Comments
 (0)