Collection.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <?php
  2. /**
  3. * Mageplaza
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Mageplaza.com license that is
  8. * available through the world-wide-web at this URL:
  9. * https://www.mageplaza.com/LICENSE.txt
  10. *
  11. * DISCLAIMER
  12. *
  13. * Do not edit or add to this file if you wish to upgrade this extension to newer
  14. * version in the future.
  15. *
  16. * @category Mageplaza
  17. * @package Mageplaza_LayeredNavigation
  18. * @copyright Copyright (c) Mageplaza (https://www.mageplaza.com/)
  19. * @license https://www.mageplaza.com/LICENSE.txt
  20. */
  21. namespace Mageplaza\LayeredNavigation\Model\ResourceModel\Fulltext;
  22. use Magento\CatalogSearch\Model\Search\RequestGenerator;
  23. use Magento\Framework\App\ObjectManager;
  24. use Magento\Framework\DB\Select;
  25. use Magento\Framework\Exception\LocalizedException;
  26. use Magento\Framework\Exception\StateException;
  27. use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
  28. /**
  29. * Class Collection
  30. * @package Mageplaza\LayeredNavigation\Model\ResourceModel\Fulltext
  31. */
  32. class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
  33. {
  34. /** @var \Mageplaza\LayeredNavigation\Model\ResourceModel\Fulltext\Collection|null Clone collection */
  35. public $collectionClone = null;
  36. /** @var string */
  37. private $queryText;
  38. /** @var string|null */
  39. private $order = null;
  40. /** @var string */
  41. private $searchRequestName;
  42. /** @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory */
  43. private $temporaryStorageFactory;
  44. /** @var \Magento\Search\Api\SearchInterface */
  45. private $search;
  46. /** @var \Mageplaza\LayeredNavigation\Model\Search\SearchCriteriaBuilder */
  47. private $searchCriteriaBuilder;
  48. /** @var \Magento\Framework\Api\Search\SearchResultInterface */
  49. private $searchResult;
  50. /** @var \Magento\Framework\Api\FilterBuilder */
  51. private $filterBuilder;
  52. /**
  53. * Collection constructor.
  54. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
  55. * @param \Psr\Log\LoggerInterface $logger
  56. * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
  57. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  58. * @param \Magento\Eav\Model\Config $eavConfig
  59. * @param \Magento\Framework\App\ResourceConnection $resource
  60. * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory
  61. * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
  62. * @param \Magento\Framework\Validator\UniversalFactory $universalFactory
  63. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  64. * @param \Magento\Framework\Module\Manager $moduleManager
  65. * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState
  66. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  67. * @param \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory
  68. * @param \Magento\Catalog\Model\ResourceModel\Url $catalogUrl
  69. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  70. * @param \Magento\Customer\Model\Session $customerSession
  71. * @param \Magento\Framework\Stdlib\DateTime $dateTime
  72. * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
  73. * @param \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory
  74. * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
  75. * @param string $searchRequestName
  76. */
  77. public function __construct(
  78. \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
  79. \Psr\Log\LoggerInterface $logger,
  80. \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
  81. \Magento\Framework\Event\ManagerInterface $eventManager,
  82. \Magento\Eav\Model\Config $eavConfig,
  83. \Magento\Framework\App\ResourceConnection $resource,
  84. \Magento\Eav\Model\EntityFactory $eavEntityFactory,
  85. \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
  86. \Magento\Framework\Validator\UniversalFactory $universalFactory,
  87. \Magento\Store\Model\StoreManagerInterface $storeManager,
  88. \Magento\Framework\Module\Manager $moduleManager,
  89. \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState,
  90. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  91. \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory,
  92. \Magento\Catalog\Model\ResourceModel\Url $catalogUrl,
  93. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  94. \Magento\Customer\Model\Session $customerSession,
  95. \Magento\Framework\Stdlib\DateTime $dateTime,
  96. \Magento\Customer\Api\GroupManagementInterface $groupManagement,
  97. \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory,
  98. \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
  99. $searchRequestName = 'catalog_view_container'
  100. )
  101. {
  102. parent::__construct(
  103. $entityFactory,
  104. $logger,
  105. $fetchStrategy,
  106. $eventManager,
  107. $eavConfig,
  108. $resource,
  109. $eavEntityFactory,
  110. $resourceHelper,
  111. $universalFactory,
  112. $storeManager,
  113. $moduleManager,
  114. $catalogProductFlatState,
  115. $scopeConfig,
  116. $productOptionFactory,
  117. $catalogUrl,
  118. $localeDate,
  119. $customerSession,
  120. $dateTime,
  121. $groupManagement,
  122. $connection
  123. );
  124. $this->temporaryStorageFactory = $temporaryStorageFactory;
  125. $this->searchRequestName = $searchRequestName;
  126. }
  127. /**
  128. * MP LayerNavigation Clone collection
  129. *
  130. * @return \Mageplaza\LayeredNavigation\Model\ResourceModel\Fulltext\Collection|null
  131. */
  132. public function getCollectionClone()
  133. {
  134. if ($this->collectionClone === null) {
  135. $this->collectionClone = clone $this;
  136. $this->collectionClone->setSearchCriteriaBuilder($this->searchCriteriaBuilder->cloneObject());
  137. }
  138. $searchCriterialBuilder = $this->collectionClone->getSearchCriteriaBuilder()->cloneObject();
  139. /** @var \Mageplaza\LayeredNavigation\Model\ResourceModel\Fulltext\Collection $collectionClone */
  140. $collectionClone = clone $this->collectionClone;
  141. $collectionClone->setSearchCriteriaBuilder($searchCriterialBuilder);
  142. return $collectionClone;
  143. }
  144. /**
  145. * MP LayerNavigation Add multi-filter categories
  146. *
  147. * @param $categories
  148. * @return $this
  149. */
  150. public function addLayerCategoryFilter($categories)
  151. {
  152. if ($this->getSearchEngine() == 'elasticsearch') {
  153. $this->addFieldToFilter('category_ids', ['in' => $categories]);
  154. } else {
  155. $this->addFieldToFilter('category_ids', implode(',', $categories));
  156. }
  157. return $this;
  158. }
  159. /**
  160. * MP LayerNavigation remove filter to load option item data
  161. *
  162. * @param $attributeCode
  163. * @return $this
  164. */
  165. public function removeAttributeSearch($attributeCode)
  166. {
  167. if (is_array($attributeCode)) {
  168. foreach ($attributeCode as $attCode) {
  169. $this->searchCriteriaBuilder->removeFilter($attCode);
  170. }
  171. } else {
  172. $this->searchCriteriaBuilder->removeFilter($attributeCode);
  173. }
  174. $this->_isFiltersRendered = false;
  175. return $this->loadWithFilter();
  176. }
  177. /**
  178. * MP LayerNavigation Get attribute condition sql
  179. *
  180. * @param $attribute
  181. * @param $condition
  182. * @param string $joinType
  183. * @return string
  184. */
  185. public function getAttributeConditionSql($attribute, $condition, $joinType = 'inner')
  186. {
  187. return $this->_getAttributeConditionSql($attribute, $condition, $joinType);
  188. }
  189. /**
  190. * MP LayerNavigation Reset Total records
  191. *
  192. * @return $this
  193. */
  194. public function resetTotalRecords()
  195. {
  196. $this->_totalRecords = null;
  197. return $this;
  198. }
  199. /**
  200. * @deprecated
  201. * @return \Magento\Search\Api\SearchInterface
  202. */
  203. private function getSearch()
  204. {
  205. if ($this->search === null) {
  206. $this->search = ObjectManager::getInstance()->get('\Magento\Search\Api\SearchInterface');
  207. }
  208. return $this->search;
  209. }
  210. /**
  211. * @deprecated
  212. * @param \Magento\Search\Api\SearchInterface $object
  213. * @return void
  214. */
  215. public function setSearch(\Magento\Search\Api\SearchInterface $object)
  216. {
  217. $this->search = $object;
  218. }
  219. /**
  220. * @deprecated
  221. * @return \Mageplaza\LayeredNavigation\Model\Search\SearchCriteriaBuilder
  222. */
  223. public function getSearchCriteriaBuilder()
  224. {
  225. if ($this->searchCriteriaBuilder === null) {
  226. $this->searchCriteriaBuilder = ObjectManager::getInstance()
  227. ->get('\Mageplaza\LayeredNavigation\Model\Search\SearchCriteriaBuilder');
  228. }
  229. return $this->searchCriteriaBuilder;
  230. }
  231. /**
  232. * @param \Mageplaza\LayeredNavigation\Model\Search\SearchCriteriaBuilder $object
  233. */
  234. public function setSearchCriteriaBuilder(\Mageplaza\LayeredNavigation\Model\Search\SearchCriteriaBuilder $object)
  235. {
  236. $this->searchCriteriaBuilder = $object;
  237. }
  238. /**
  239. * @deprecated
  240. * @return \Magento\Framework\Api\FilterBuilder
  241. */
  242. private function getFilterBuilder()
  243. {
  244. if ($this->filterBuilder === null) {
  245. $this->filterBuilder = ObjectManager::getInstance()->get('\Magento\Framework\Api\FilterBuilder');
  246. }
  247. return $this->filterBuilder;
  248. }
  249. /**
  250. * @deprecated
  251. * @param \Magento\Framework\Api\FilterBuilder $object
  252. * @return void
  253. */
  254. public function setFilterBuilder(\Magento\Framework\Api\FilterBuilder $object)
  255. {
  256. $this->filterBuilder = $object;
  257. }
  258. /**
  259. * Apply attribute filter to facet collection
  260. *
  261. * @param string $field
  262. * @param null $condition
  263. * @return $this
  264. */
  265. public function addFieldToFilter($field, $condition = null)
  266. {
  267. if ($this->searchResult !== null) {
  268. throw new \RuntimeException('Illegal state');
  269. }
  270. $this->getSearchCriteriaBuilder();
  271. $this->getFilterBuilder();
  272. if (isset($condition['in']) && $this->getSearchEngine() == 'elasticsearch') {
  273. $this->filterBuilder->setField($field);
  274. $this->filterBuilder->setValue($condition['in']);
  275. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  276. } else {
  277. if (!is_array($condition) || !in_array(key($condition), ['from', 'to'])) {
  278. $this->filterBuilder->setField($field);
  279. $this->filterBuilder->setValue($condition);
  280. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  281. } else {
  282. if (!empty($condition['from'])) {
  283. $this->filterBuilder->setField("{$field}.from");
  284. $this->filterBuilder->setValue($condition['from']);
  285. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  286. }
  287. if (!empty($condition['to'])) {
  288. $this->filterBuilder->setField("{$field}.to");
  289. $this->filterBuilder->setValue($condition['to']);
  290. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  291. }
  292. }
  293. }
  294. return $this;
  295. }
  296. /**
  297. * Add search query filter
  298. *
  299. * @param string $query
  300. * @return $this
  301. */
  302. public function addSearchFilter($query)
  303. {
  304. $this->queryText = trim($this->queryText . ' ' . $query);
  305. return $this;
  306. }
  307. /**
  308. * @inheritdoc
  309. */
  310. protected function _renderFiltersBefore()
  311. {
  312. $this->getCollectionClone();
  313. $this->getSearchCriteriaBuilder();
  314. $this->getFilterBuilder();
  315. $this->getSearch();
  316. if ($this->queryText) {
  317. $this->filterBuilder->setField('search_term');
  318. $this->filterBuilder->setValue($this->queryText);
  319. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  320. }
  321. $priceRangeCalculation = $this->_scopeConfig->getValue(
  322. \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION,
  323. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  324. );
  325. if ($priceRangeCalculation) {
  326. $this->filterBuilder->setField('price_dynamic_algorithm');
  327. $this->filterBuilder->setValue('auto');
  328. $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
  329. }
  330. $searchCriteria = $this->searchCriteriaBuilder->create();
  331. $searchCriteria->setRequestName($this->searchRequestName);
  332. try {
  333. $this->searchResult = $this->getSearch()->search($searchCriteria);
  334. } catch (\Exception $e) {
  335. throw new LocalizedException(__('Sorry, something went wrong. You can find out more in the error log.'));
  336. }
  337. $temporaryStorage = $this->temporaryStorageFactory->create();
  338. $table = $temporaryStorage->storeDocuments($this->searchResult->getItems());
  339. $this->getSelect()->joinInner(
  340. [
  341. 'search_result' => $table->getName(),
  342. ],
  343. 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
  344. []
  345. );
  346. if ($this->order && 'relevance' === $this->order['field']) {
  347. $this->getSelect()->order('search_result.' . TemporaryStorage::FIELD_SCORE . ' ' . $this->order['dir']);
  348. }
  349. parent::_renderFiltersBefore();
  350. }
  351. /**
  352. * @return $this
  353. */
  354. protected function _renderFilters()
  355. {
  356. $this->_filters = [];
  357. return parent::_renderFilters();
  358. }
  359. /**
  360. * sort product before load
  361. */
  362. protected function _beforeLoad()
  363. {
  364. $this->setOrder('entity_id');
  365. return parent::_beforeLoad();
  366. }
  367. /**
  368. * Set Order field
  369. *
  370. * @param string $attribute
  371. * @param string $dir
  372. * @return $this
  373. */
  374. public function setOrder($attribute, $dir = Select::SQL_DESC)
  375. {
  376. $this->order = ['field' => $attribute, 'dir' => $dir];
  377. if ($attribute != 'relevance') {
  378. parent::setOrder($attribute, $dir);
  379. }
  380. return $this;
  381. }
  382. /**
  383. * Stub method for compatibility with other search engines
  384. *
  385. * @return $this
  386. */
  387. public function setGeneralDefaultQuery()
  388. {
  389. return $this;
  390. }
  391. /**
  392. * Return field faceted data from faceted search result
  393. *
  394. * @param string $field
  395. * @return array
  396. * @throws StateException
  397. */
  398. public function getFacetedData($field)
  399. {
  400. $this->_renderFilters();
  401. $result = [];
  402. $aggregations = $this->searchResult->getAggregations();
  403. // This behavior is for case with empty object when we got EmptyRequestDataException
  404. if (null !== $aggregations) {
  405. $bucket = $aggregations->getBucket($field . RequestGenerator::BUCKET_SUFFIX);
  406. if ($bucket) {
  407. foreach ($bucket->getValues() as $value) {
  408. $metrics = $value->getMetrics();
  409. $result[$metrics['value']] = $metrics;
  410. }
  411. } else {
  412. throw new StateException(__('Bucket does not exist'));
  413. }
  414. }
  415. return $result;
  416. }
  417. /**
  418. * Specify category filter for product collection
  419. *
  420. * @param \Magento\Catalog\Model\Category $category
  421. * @return $this
  422. */
  423. public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
  424. {
  425. $this->addFieldToFilter('category_ids', $category->getId());
  426. return parent::addCategoryFilter($category);
  427. }
  428. /**
  429. * Set product visibility filter for enabled products
  430. *
  431. * @param array $visibility
  432. * @return $this
  433. */
  434. public function setVisibility($visibility)
  435. {
  436. $this->addFieldToFilter('visibility', $visibility);
  437. return parent::setVisibility($visibility);
  438. }
  439. /**
  440. * Get Search Engine Config
  441. *
  442. * @return string
  443. */
  444. public function getSearchEngine()
  445. {
  446. return $this->_scopeConfig->getValue(
  447. \Magento\Config\Model\Config\Backend\Admin\Custom::XML_PATH_CATALOG_SEARCH_ENGINE,
  448. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  449. );
  450. }
  451. }