diff --git a/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php b/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php index 8f0a3fb2baa69..007fcb7a6f509 100644 --- a/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php +++ b/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php @@ -5,11 +5,14 @@ */ namespace Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor; +use Magento\Framework\Api\Filter; use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\DB\Select; +use Zend_Db_Select_Exception; /** * SearchCriteria FilterProcessor @@ -58,29 +61,108 @@ public function process(SearchCriteriaInterface $searchCriteria, AbstractDb $col * @param FilterGroup $filterGroup * @param AbstractDb $collection * @return void + * @throws Zend_Db_Select_Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ private function addFilterGroupToCollection( FilterGroup $filterGroup, AbstractDb $collection ) { $fields = []; + $customFilters = []; + $applyLater = false; foreach ($filterGroup->getFilters() as $filter) { $isApplied = false; $customFilter = $this->getCustomFilterForField($filter->getField()); if ($customFilter) { - $isApplied = $customFilter->apply($filter, $collection); + if ($filter->getConditionType() === 'eq') { + $customFilters = array_map( + function ($customFilter) { + if (count($values = $customFilter['values']) > 1) { + $filter = reset($customFilter['filter']); + $filter->setValue(implode(',', $values)); + $filter->setConditionType('in'); + $customFilter['filter'] = [$filter]; + } + + return $customFilter; + }, + array_merge_recursive( + $customFilters, + [$filter->getField() => [ + 'filter' => [clone $filter], + 'values' => [$filter->getValue()] + ]] + ) + ); + + $applyLater = true; + } + + if (!$applyLater) { + $isApplied = $customFilter->apply($filter, $collection); + } } - if (!$isApplied) { + if (!$isApplied && !$customFilter) { $field = $this->getFieldMapping($filter->getField()); $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; $fields[] = ['attribute' => $field, $condition => $filter->getValue()]; } } - if ($fields) { + $whereParts = $collection->getSelect()->getPart(Select::WHERE); + $whereSql = $this->applyFilters($collection, $fields, $customFilters); + $collection->getSelect()->setPart(Select::WHERE, $whereParts); + if ($whereSql) { + $collection->getSelect()->where($whereSql); + } + } + + /** + * Apply filters and retrieve `where` conditions + * + * @param AbstractDb $collection + * @param array $fields + * @param array $customFilters + * @return string + * @throws Zend_Db_Select_Exception + */ + private function applyFilters( + AbstractDb $collection, + array $fields, + array $customFilters + ): string { + $select = $collection->getSelect(); + $select->reset(Select::WHERE); + $whereParts = []; + if (count($fields)) { $collection->addFieldToFilter($fields); + $whereParts[] = $select->getPart(Select::WHERE)[0]; + $select->reset(Select::WHERE); + } + + if (count($customFilters)) { + foreach ($customFilters as $field => $filter) { + $customFilter = $this->getCustomFilterForField($field); + /** @var Filter $filter */ + $filter = reset($filter['filter']); + $customFilter->apply($filter, $collection); + $whereCondition = $select->getPart(Select::WHERE); + if (is_array($whereCondition) && count($whereCondition)) { + $whereParts[] = $whereCondition[0]; + $select->reset(Select::WHERE); + } + } + } + $resultCondition = ''; + + if (count($whereParts)) { + $resultCondition = '(' . implode(') ' . Select::SQL_OR . ' (', $whereParts) . ')'; } + + return $resultCondition; } /** diff --git a/app/code/Magento/Eav/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessorTest.php b/app/code/Magento/Eav/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessorTest.php index 28c52ad704f2b..ee522283eb394 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessorTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessorTest.php @@ -7,36 +7,30 @@ namespace Magento\Eav\Test\Unit\Model\Api\SearchCriteria\CollectionProcessor; +use InvalidArgumentException; use Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor; use Magento\Framework\Api\Filter; use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\DB\Select; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use stdClass; class FilterProcessorTest extends TestCase { - /** - * Return model - * - * @param CustomFilterInterface[] $customFilters - * @param array $fieldMapping - * @return FilterProcessor - */ - private function getModel(array $customFilters, array $fieldMapping) - { - return new FilterProcessor($customFilters, $fieldMapping); - } - /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testProcess() + public function testProcess(): void { /** @var CustomFilterInterface|MockObject $customFilterMock */ - $customFilterMock = $this->createPartialMock(CustomFilterInterface::class, ['apply']); + $customFilterMock = $this->createPartialMock( + CustomFilterInterface::class, + ['apply'] + ); $customFilterField = 'customFilterField'; $customFilters = [$customFilterField => $customFilterMock]; @@ -145,14 +139,21 @@ public function testProcess() [$resultTwo] )->willReturnSelf(); + $selectMock = $this->createMock(Select::class); + $collectionMock->method('getSelect')->willReturn($selectMock); + $selectMock->method('getPart') + ->with(Select::WHERE) + ->willReturn([0 => '']); + $model->process($searchCriteriaMock, $collectionMock); } - public function testProcessWithException() + public function testProcessWithException(): void { $this->expectException('InvalidArgumentException'); - /** @var \stdClass|MockObject $customFilterMock */ - $customFilterMock = $this->getMockBuilder(\stdClass::class)->addMethods(['apply']) + /** @var stdClass|MockObject $customFilterMock */ + $customFilterMock = $this->getMockBuilder(stdClass::class) + ->addMethods(['apply']) ->disableOriginalConstructor() ->getMock(); @@ -199,4 +200,18 @@ public function testProcessWithException() $model->process($searchCriteriaMock, $collectionMock); } + + /** + * Returns model + * + * @param CustomFilterInterface[] $customFilters + * @param array $fieldMapping + * @return FilterProcessor + */ + private function getModel( + array $customFilters, + array $fieldMapping + ): FilterProcessor { + return new FilterProcessor($customFilters, $fieldMapping); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 1b18949b0ac5b..069be33aedcf1 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -220,6 +220,7 @@ public function productCreationProvider() private function loadWebsiteByCode($websiteCode) { $websiteRepository = Bootstrap::getObjectManager()->get(WebsiteRepository::class); + $website = null; try { $website = $websiteRepository->get($websiteCode); } catch (NoSuchEntityException $e) { @@ -932,6 +933,120 @@ public function testGetList() $this->assertEquals($expectedResult, $response['items'][0]['custom_attributes'][$index]['value']); } + /** + * Test getList() method with 'OR' criteria. + * + * @param array $searchCriteria + * @param int $totalCount + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @dataProvider getListOrCriteriaDataProvider + */ + public function testGetListOrCriteria(array $searchCriteria, int $totalCount) + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($searchCriteria), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'GetList', + ], + ]; + + $response = $this->_webApiCall($serviceInfo, $searchCriteria); + + $this->assertArrayHasKey('search_criteria', $response); + $this->assertArrayHasKey('total_count', $response); + $this->assertArrayHasKey('items', $response); + + $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']); + $this->assertEquals($response['total_count'], $totalCount); + $this->assertEquals(count($response['items']), $totalCount); + } + + /** + * Data provider for testGetListOrCriteria. + * + * @return array + */ + public function getListOrCriteriaDataProvider(): array + { + return [ + [ + [ + 'searchCriteria' => [ + 'filter_groups' => [ + [ + 'filters' => [ + [ + 'field' => 'category_id', + 'value' => '12', + 'condition_type' => 'eq', + ], + ], + ], + ], + 'current_page' => 1, + 'page_size' => 10, + ], + ], + 2, + ], + [ + [ + 'searchCriteria' => [ + 'filter_groups' => [ + [ + 'filters' => [ + [ + 'field' => 'category_id', + 'value' => '12', + 'condition_type' => 'eq', + ], + [ + 'field' => 'category_id', + 'value' => '13', + 'condition_type' => 'eq', + ], + ], + ], + ], + 'current_page' => 1, + 'page_size' => 10, + ], + ], + 3, + ], + [ + [ + 'searchCriteria' => [ + 'filter_groups' => [ + [ + 'filters' => [ + [ + 'field' => 'category_id', + 'value' => '12', + 'condition_type' => 'eq', + ], + [ + 'field' => 'sku', + 'value' => 'simple', + 'condition_type' => 'eq', + ], + ], + ], + ], + 'current_page' => 1, + 'page_size' => 10, + ], + ], + 3, + ], + ]; + } + /** * Test getList() method with additional params *