Collection.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CatalogSearch\Model\ResourceModel\Advanced;
  7. use Magento\Catalog\Model\Product;
  8. use Magento\Framework\Api\FilterBuilder;
  9. use Magento\Framework\Api\Search\SearchCriteriaBuilder;
  10. use Magento\Framework\Api\Search\SearchResultFactory;
  11. use Magento\Framework\EntityManager\MetadataPool;
  12. use Magento\Framework\Exception\LocalizedException;
  13. use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
  14. use Magento\Framework\Search\Request\EmptyRequestDataException;
  15. use Magento\Framework\Search\Request\NonExistingRequestNameException;
  16. use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
  17. /**
  18. * Advanced search collection
  19. *
  20. * This collection should be refactored to not have dependencies on MySQL-specific implementation.
  21. *
  22. * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
  23. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  24. * @api
  25. * @since 100.0.2
  26. */
  27. class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
  28. {
  29. /**
  30. * List Of filters
  31. * @var array
  32. */
  33. private $filters = [];
  34. /**
  35. * @var \Magento\Search\Api\SearchInterface
  36. */
  37. private $search;
  38. /**
  39. * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory
  40. * @deprecated 101.0.0 There must be no dependencies on specific adapter in generic search implementation
  41. */
  42. private $temporaryStorageFactory;
  43. /**
  44. * @var SearchCriteriaBuilder
  45. */
  46. private $searchCriteriaBuilder;
  47. /**
  48. * @var SearchResultFactory
  49. */
  50. private $searchResultFactory;
  51. /**
  52. * @var FilterBuilder
  53. */
  54. private $filterBuilder;
  55. /**
  56. * Collection constructor
  57. *
  58. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
  59. * @param \Psr\Log\LoggerInterface $logger
  60. * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
  61. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  62. * @param \Magento\Eav\Model\Config $eavConfig
  63. * @param \Magento\Framework\App\ResourceConnection $resource
  64. * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory
  65. * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
  66. * @param \Magento\Framework\Validator\UniversalFactory $universalFactory
  67. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  68. * @param \Magento\Framework\Module\Manager $moduleManager
  69. * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState
  70. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  71. * @param Product\OptionFactory $productOptionFactory
  72. * @param \Magento\Catalog\Model\ResourceModel\Url $catalogUrl
  73. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  74. * @param \Magento\Customer\Model\Session $customerSession
  75. * @param \Magento\Framework\Stdlib\DateTime $dateTime
  76. * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
  77. * @param \Magento\CatalogSearch\Model\Advanced\Request\Builder $requestBuilder
  78. * @param \Magento\Search\Model\SearchEngine $searchEngine
  79. * @param \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory
  80. * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
  81. * @param SearchResultFactory|null $searchResultFactory
  82. * @param ProductLimitationFactory|null $productLimitationFactory
  83. * @param MetadataPool|null $metadataPool
  84. *
  85. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  86. */
  87. public function __construct(
  88. \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
  89. \Psr\Log\LoggerInterface $logger,
  90. \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
  91. \Magento\Framework\Event\ManagerInterface $eventManager,
  92. \Magento\Eav\Model\Config $eavConfig,
  93. \Magento\Framework\App\ResourceConnection $resource,
  94. \Magento\Eav\Model\EntityFactory $eavEntityFactory,
  95. \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
  96. \Magento\Framework\Validator\UniversalFactory $universalFactory,
  97. \Magento\Store\Model\StoreManagerInterface $storeManager,
  98. \Magento\Framework\Module\Manager $moduleManager,
  99. \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState,
  100. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  101. \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory,
  102. \Magento\Catalog\Model\ResourceModel\Url $catalogUrl,
  103. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  104. \Magento\Customer\Model\Session $customerSession,
  105. \Magento\Framework\Stdlib\DateTime $dateTime,
  106. \Magento\Customer\Api\GroupManagementInterface $groupManagement,
  107. \Magento\CatalogSearch\Model\Advanced\Request\Builder $requestBuilder,
  108. \Magento\Search\Model\SearchEngine $searchEngine,
  109. \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory,
  110. \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
  111. SearchResultFactory $searchResultFactory = null,
  112. ProductLimitationFactory $productLimitationFactory = null,
  113. MetadataPool $metadataPool = null
  114. ) {
  115. $this->requestBuilder = $requestBuilder;
  116. $this->searchEngine = $searchEngine;
  117. $this->temporaryStorageFactory = $temporaryStorageFactory;
  118. if ($searchResultFactory === null) {
  119. $this->searchResultFactory = \Magento\Framework\App\ObjectManager::getInstance()
  120. ->get(\Magento\Framework\Api\Search\SearchResultFactory::class);
  121. }
  122. parent::__construct(
  123. $entityFactory,
  124. $logger,
  125. $fetchStrategy,
  126. $eventManager,
  127. $eavConfig,
  128. $resource,
  129. $eavEntityFactory,
  130. $resourceHelper,
  131. $universalFactory,
  132. $storeManager,
  133. $moduleManager,
  134. $catalogProductFlatState,
  135. $scopeConfig,
  136. $productOptionFactory,
  137. $catalogUrl,
  138. $localeDate,
  139. $customerSession,
  140. $dateTime,
  141. $groupManagement,
  142. $connection,
  143. $productLimitationFactory,
  144. $metadataPool
  145. );
  146. }
  147. /**
  148. * Add not indexable fields to search
  149. *
  150. * @param array $fields
  151. * @return $this
  152. * @throws \Magento\Framework\Exception\LocalizedException
  153. */
  154. public function addFieldsToFilter($fields)
  155. {
  156. if ($fields) {
  157. $this->filters = array_merge($this->filters, $fields);
  158. }
  159. return $this;
  160. }
  161. /**
  162. * @inheritdoc
  163. */
  164. protected function _renderFiltersBefore()
  165. {
  166. if ($this->filters) {
  167. foreach ($this->filters as $attributes) {
  168. foreach ($attributes as $attributeCode => $attributeValue) {
  169. $attributeCode = $this->getAttributeCode($attributeCode);
  170. $this->addAttributeToSearch($attributeCode, $attributeValue);
  171. }
  172. }
  173. $searchCriteria = $this->getSearchCriteriaBuilder()->create();
  174. $searchCriteria->setRequestName('advanced_search_container');
  175. try {
  176. $searchResult = $this->getSearch()->search($searchCriteria);
  177. } catch (EmptyRequestDataException $e) {
  178. /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */
  179. $searchResult = $this->searchResultFactory->create()->setItems([]);
  180. } catch (NonExistingRequestNameException $e) {
  181. $this->_logger->error($e->getMessage());
  182. throw new LocalizedException(
  183. __('An error occurred. For details, see the error log.')
  184. );
  185. }
  186. $temporaryStorage = $this->temporaryStorageFactory->create();
  187. $table = $temporaryStorage->storeApiDocuments($searchResult->getItems());
  188. $this->getSelect()->joinInner(
  189. [
  190. 'search_result' => $table->getName(),
  191. ],
  192. 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
  193. []
  194. );
  195. }
  196. parent::_renderFiltersBefore();
  197. }
  198. /**
  199. * Get attribute code.
  200. *
  201. * @param string $attributeCode
  202. * @return string
  203. */
  204. private function getAttributeCode($attributeCode)
  205. {
  206. if (is_numeric($attributeCode)) {
  207. $attributeCode = $this->_eavConfig->getAttribute(Product::ENTITY, $attributeCode)
  208. ->getAttributeCode();
  209. }
  210. return $attributeCode;
  211. }
  212. /**
  213. * Create a filter and add it to the SearchCriteriaBuilder.
  214. *
  215. * @param string $attributeCode
  216. * @param array|string $attributeValue
  217. * @return void
  218. */
  219. private function addAttributeToSearch($attributeCode, $attributeValue)
  220. {
  221. if (isset($attributeValue['from']) || isset($attributeValue['to'])) {
  222. $this->addRangeAttributeToSearch($attributeCode, $attributeValue);
  223. } elseif (!is_array($attributeValue)) {
  224. $this->getFilterBuilder()->setField($attributeCode)->setValue($attributeValue);
  225. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  226. } elseif (isset($attributeValue['like'])) {
  227. $this->getFilterBuilder()->setField($attributeCode)->setValue($attributeValue['like']);
  228. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  229. } elseif (isset($attributeValue['in'])) {
  230. $this->getFilterBuilder()->setField($attributeCode)->setValue($attributeValue['in']);
  231. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  232. } elseif (isset($attributeValue['in_set'])) {
  233. $this->getFilterBuilder()->setField($attributeCode)->setValue($attributeValue['in_set']);
  234. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  235. }
  236. }
  237. /**
  238. * Add attributes that have a range (from,to) to the SearchCriteriaBuilder.
  239. *
  240. * @param string $attributeCode
  241. * @param array|string $attributeValue
  242. * @return void
  243. */
  244. private function addRangeAttributeToSearch($attributeCode, $attributeValue)
  245. {
  246. if (isset($attributeValue['from']) && '' !== $attributeValue['from']) {
  247. $this->getFilterBuilder()->setField("{$attributeCode}.from")->setValue($attributeValue['from']);
  248. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  249. }
  250. if (isset($attributeValue['to']) && '' !== $attributeValue['to']) {
  251. $this->getFilterBuilder()->setField("{$attributeCode}.to")->setValue($attributeValue['to']);
  252. $this->getSearchCriteriaBuilder()->addFilter($this->getFilterBuilder()->create());
  253. }
  254. }
  255. /**
  256. * Get search.
  257. *
  258. * @return \Magento\Search\Api\SearchInterface
  259. */
  260. private function getSearch()
  261. {
  262. if (null === $this->search) {
  263. $this->search = \Magento\Framework\App\ObjectManager::getInstance()
  264. ->get(\Magento\Search\Api\SearchInterface::class);
  265. }
  266. return $this->search;
  267. }
  268. /**
  269. * Get search criteria builder.
  270. *
  271. * @return SearchCriteriaBuilder
  272. */
  273. private function getSearchCriteriaBuilder()
  274. {
  275. if (null === $this->searchCriteriaBuilder) {
  276. $this->searchCriteriaBuilder = \Magento\Framework\App\ObjectManager::getInstance()
  277. ->get(\Magento\Framework\Api\Search\SearchCriteriaBuilder::class);
  278. }
  279. return $this->searchCriteriaBuilder;
  280. }
  281. /**
  282. * Get filter builder.
  283. *
  284. * @return FilterBuilder
  285. */
  286. private function getFilterBuilder()
  287. {
  288. if (null === $this->filterBuilder) {
  289. $this->filterBuilder = \Magento\Framework\App\ObjectManager::getInstance()
  290. ->get(\Magento\Framework\Api\FilterBuilder::class);
  291. }
  292. return $this->filterBuilder;
  293. }
  294. }