Skip to content

Commit e8a622b

Browse files
MC-30304: CSV product export ignores stock filter
1 parent c2e2646 commit e8a622b

File tree

8 files changed

+367
-11
lines changed

8 files changed

+367
-11
lines changed

app/code/Magento/CatalogImportExport/Model/Export/Product.php

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
*/
66
namespace Magento\CatalogImportExport\Model\Export;
77

8+
use Magento\Catalog\Model\Product as ProductEntity;
89
use Magento\Catalog\Model\ResourceModel\Product\Option\Collection;
10+
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
911
use Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor;
12+
use Magento\Framework\App\ObjectManager;
1013
use Magento\ImportExport\Model\Import;
11-
use \Magento\Store\Model\Store;
12-
use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
13-
use Magento\Catalog\Model\Product as ProductEntity;
14+
use Magento\Store\Model\Store;
1415

1516
/**
1617
* Export entity product model
@@ -21,6 +22,8 @@
2122
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
2223
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2324
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
25+
* @SuppressWarnings(PHPMD.ExcessiveClassLength)
26+
* @SuppressWarnings(PHPMD.TooManyMethods)
2427
* @since 100.0.2
2528
*/
2629
class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
@@ -348,6 +351,10 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
348351
* @var string
349352
*/
350353
private $productEntityLinkField;
354+
/**
355+
* @var ProductFilterInterface
356+
*/
357+
private $filter;
351358

352359
/**
353360
* Product constructor.
@@ -369,6 +376,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
369376
* @param ProductEntity\LinkTypeProvider $linkTypeProvider
370377
* @param RowCustomizerInterface $rowCustomizer
371378
* @param array $dateAttrCodes
379+
* @param ProductFilterInterface $filter
372380
* @throws \Magento\Framework\Exception\LocalizedException
373381
*/
374382
public function __construct(
@@ -388,7 +396,8 @@ public function __construct(
388396
\Magento\CatalogImportExport\Model\Export\Product\Type\Factory $_typeFactory,
389397
\Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider,
390398
\Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer,
391-
array $dateAttrCodes = []
399+
array $dateAttrCodes = [],
400+
?ProductFilterInterface $filter = null
392401
) {
393402
$this->_entityCollectionFactory = $collectionFactory;
394403
$this->_exportConfig = $exportConfig;
@@ -404,6 +413,7 @@ public function __construct(
404413
$this->_linkTypeProvider = $linkTypeProvider;
405414
$this->rowCustomizer = $rowCustomizer;
406415
$this->dateAttrCodes = array_merge($this->dateAttrCodes, $dateAttrCodes);
416+
$this->filter = $filter ?? ObjectManager::getInstance()->get(ProductFilterInterface::class);
407417

408418
parent::__construct($localeDate, $config, $resource, $storeManager);
409419

@@ -819,9 +829,11 @@ protected function getItemsPerPage()
819829
case 'g':
820830
$memoryLimit *= 1024;
821831
// fall-through intentional
832+
// no break
822833
case 'm':
823834
$memoryLimit *= 1024;
824835
// fall-through intentional
836+
// no break
825837
case 'k':
826838
$memoryLimit *= 1024;
827839
break;
@@ -913,12 +925,7 @@ protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection
913925
$exportFilter = !empty($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]) ?
914926
$this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP] : [];
915927

916-
if (isset($exportFilter['category_ids'])
917-
&& trim($exportFilter['category_ids'])
918-
&& $collection instanceof \Magento\Catalog\Model\ResourceModel\Product\Collection
919-
) {
920-
$collection->addCategoriesFilter(['in' => explode(',', $exportFilter['category_ids'])]);
921-
}
928+
$collection = $this->filter->filter($collection, $exportFilter);
922929

923930
return parent::_prepareEntityCollection($collection);
924931
}
@@ -979,7 +986,6 @@ protected function loadCollection(): array
979986
$collection = $this->_getEntityCollection();
980987
foreach (array_keys($this->_storeIdToCode) as $storeId) {
981988
$collection->setOrder('entity_id', 'asc');
982-
$this->_prepareEntityCollection($collection);
983989
$collection->setStoreId($storeId);
984990
$collection->load();
985991
foreach ($collection as $itemId => $item) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\CatalogImportExport\Model\Export\Product;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
11+
use Magento\CatalogImportExport\Model\Export\ProductFilterInterface;
12+
13+
/**
14+
* Category filter for products export
15+
*/
16+
class CategoryFilter implements ProductFilterInterface
17+
{
18+
private const NAME = 'category_ids';
19+
20+
/**
21+
* @inheritDoc
22+
*/
23+
public function filter(Collection $collection, array $filters): Collection
24+
{
25+
$value = trim($filters[self::NAME] ?? '');
26+
if ($value) {
27+
$collection->addCategoriesFilter(['in' => explode(',', $value)]);
28+
}
29+
return $collection;
30+
}
31+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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\CatalogImportExport\Model\Export\Product;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
11+
use Magento\CatalogInventory\Model\Configuration;
12+
use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResourceModel;
13+
use Magento\Framework\App\Config\ScopeConfigInterface;
14+
use Magento\Store\Model\ScopeInterface;
15+
16+
/**
17+
* Stock status collection filter
18+
*/
19+
class Stock
20+
{
21+
/**
22+
* @var ScopeConfigInterface
23+
*/
24+
private $scopeConfig;
25+
/**
26+
* @var StockItemResourceModel
27+
*/
28+
private $stockItemResourceModel;
29+
30+
/**
31+
* @param ScopeConfigInterface $scopeConfig
32+
* @param StockItemResourceModel $stockItemResourceModel
33+
*/
34+
public function __construct(
35+
ScopeConfigInterface $scopeConfig,
36+
StockItemResourceModel $stockItemResourceModel
37+
) {
38+
$this->scopeConfig = $scopeConfig;
39+
$this->stockItemResourceModel = $stockItemResourceModel;
40+
}
41+
42+
/**
43+
* Filter provided collection to return only "in stock" products
44+
*
45+
* @param Collection $collection
46+
* @return Collection
47+
*/
48+
public function addInStockFilterToCollection(Collection $collection): Collection
49+
{
50+
$manageStock = $this->scopeConfig->getValue(
51+
Configuration::XML_PATH_MANAGE_STOCK,
52+
ScopeInterface::SCOPE_STORE
53+
);
54+
$cond = [
55+
'{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=1 AND {{table}}.is_in_stock=1',
56+
'{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=0'
57+
];
58+
59+
if ($manageStock) {
60+
$cond[] = '{{table}}.use_config_manage_stock = 1 AND {{table}}.is_in_stock=1';
61+
} else {
62+
$cond[] = '{{table}}.use_config_manage_stock = 1';
63+
}
64+
return $this->addFilterToCollection($collection, '(' . join(') OR (', $cond) . ')');
65+
}
66+
67+
/**
68+
* Filter provided collection to return only "out of stock" products
69+
*
70+
* @param Collection $collection
71+
* @return Collection
72+
*/
73+
public function addOutOfStockFilterToCollection(Collection $collection): Collection
74+
{
75+
$manageStock = $this->scopeConfig->getValue(
76+
Configuration::XML_PATH_MANAGE_STOCK,
77+
ScopeInterface::SCOPE_STORE
78+
);
79+
$cond = [
80+
'{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=1 AND {{table}}.is_in_stock=0',
81+
];
82+
83+
if ($manageStock) {
84+
$cond[] = '{{table}}.use_config_manage_stock = 1 AND {{table}}.is_in_stock=0';
85+
}
86+
return $this->addFilterToCollection($collection, '(' . join(') OR (', $cond) . ')');
87+
}
88+
89+
/**
90+
* Add stock status filter to the collection
91+
*
92+
* @param Collection $collection
93+
* @param string $condition
94+
* @return Collection
95+
*/
96+
private function addFilterToCollection(Collection $collection, string $condition): Collection
97+
{
98+
$condition = str_replace(
99+
'{{table}}',
100+
'inventory_stock_item_filter',
101+
'({{table}}.product_id=e.entity_id) AND (' . $condition . ')'
102+
);
103+
$collection->getSelect()
104+
->joinInner(
105+
['inventory_stock_item_filter' => $this->stockItemResourceModel->getMainTable()],
106+
$condition,
107+
[]
108+
);
109+
return $collection;
110+
}
111+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\CatalogImportExport\Model\Export\Product;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
11+
use Magento\CatalogImportExport\Model\Export\ProductFilterInterface;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
14+
/**
15+
* Stock status filter for products export
16+
*/
17+
class StockStatusFilter implements ProductFilterInterface
18+
{
19+
private const NAME = 'quantity_and_stock_status';
20+
private const IN_STOCK = '1';
21+
private const OUT_OF_STOCK = '0';
22+
/**
23+
* @var Stock
24+
*/
25+
private $stockHelper;
26+
/**
27+
* @var ScopeConfigInterface
28+
*/
29+
private $scopeConfig;
30+
31+
/**
32+
* @param Stock $stockHelper
33+
* @param ScopeConfigInterface $scopeConfig
34+
*/
35+
public function __construct(
36+
Stock $stockHelper,
37+
ScopeConfigInterface $scopeConfig
38+
) {
39+
$this->stockHelper = $stockHelper;
40+
$this->scopeConfig = $scopeConfig;
41+
}
42+
/**
43+
* @inheritDoc
44+
*/
45+
public function filter(Collection $collection, array $filters): Collection
46+
{
47+
$value = $filters[self::NAME] ?? '';
48+
switch ($value) {
49+
case self::IN_STOCK:
50+
$this->stockHelper->addInStockFilterToCollection($collection);
51+
break;
52+
case self::OUT_OF_STOCK:
53+
$this->stockHelper->addOutOfStockFilterToCollection($collection);
54+
break;
55+
}
56+
return $collection;
57+
}
58+
}
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\CatalogImportExport\Model\Export;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
11+
12+
interface ProductFilterInterface
13+
{
14+
/**
15+
* Filter provided product collection
16+
*
17+
* @param Collection $collection
18+
* @param array $filters
19+
* @return Collection
20+
*/
21+
public function filter(Collection $collection, array $filters): Collection;
22+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\CatalogImportExport\Model\Export;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
11+
12+
/**
13+
* Product filters pool for export
14+
*/
15+
class ProductFilters implements ProductFilterInterface
16+
{
17+
/**
18+
* @var ProductFilterInterface[]
19+
*/
20+
private $filters;
21+
/**
22+
* @param ProductFilterInterface[] $filters
23+
*/
24+
public function __construct(array $filters = [])
25+
{
26+
$this->filters = $filters;
27+
}
28+
29+
/**
30+
* @inheritDoc
31+
*/
32+
public function filter(Collection $collection, array $filters): Collection
33+
{
34+
foreach ($this->filters as $filter) {
35+
$collection = $filter->filter($collection, $filters);
36+
}
37+
return $collection;
38+
}
39+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
99
<preference for="Magento\CatalogImportExport\Model\Export\RowCustomizerInterface" type="Magento\CatalogImportExport\Model\Export\RowCustomizer\Composite" />
1010
<preference for="Magento\CatalogImportExport\Model\StockItemImporterInterface" type="Magento\CatalogImportExport\Model\StockItemImporter" />
11+
<preference for="Magento\CatalogImportExport\Model\Export\ProductFilterInterface" type="Magento\CatalogImportExport\Model\Export\ProductFilters" />
1112
<type name="Magento\ImportExport\Model\Import">
1213
<plugin name="catalogProductFlatIndexerImport" type="Magento\CatalogImportExport\Model\Indexer\Product\Flat\Plugin\Import" />
1314
<plugin name="invalidatePriceIndexerOnImport" type="Magento\CatalogImportExport\Model\Indexer\Product\Price\Plugin\Import" />
@@ -35,4 +36,12 @@
3536
<argument name="validationState" xsi:type="object">Magento\Framework\Config\ValidationState\Required</argument>
3637
</arguments>
3738
</type>
39+
<type name="Magento\CatalogImportExport\Model\Export\ProductFilters">
40+
<arguments>
41+
<argument name="filters" xsi:type="array">
42+
<item name="category_ids" xsi:type="object">Magento\CatalogImportExport\Model\Export\Product\CategoryFilter</item>
43+
<item name="quantity_and_stock_status" xsi:type="object">Magento\CatalogImportExport\Model\Export\Product\StockStatusFilter</item>
44+
</argument>
45+
</arguments>
46+
</type>
3847
</config>

0 commit comments

Comments
 (0)