PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Frontend/Modules/Search/Ajax/Autosuggest.php

http://github.com/forkcms/forkcms
PHP | 181 lines | 124 code | 34 blank | 23 comment | 11 complexity | bb16d4bf05578774e45eae66b1436886 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, MIT, AGPL-3.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. namespace Frontend\Modules\Search\Ajax;
  3. use DateInterval;
  4. use Psr\Cache\CacheItemPoolInterface;
  5. use Frontend\Core\Engine\Base\AjaxAction as FrontendBaseAJAXAction;
  6. use Frontend\Core\Language\Language as FL;
  7. use Frontend\Core\Engine\Navigation as FrontendNavigation;
  8. use Frontend\Modules\Search\Engine\Model as FrontendSearchModel;
  9. use Symfony\Component\HttpFoundation\Response;
  10. /**
  11. * This is the auto suggest-action, it will output a list of results for a certain search
  12. */
  13. class Autosuggest extends FrontendBaseAJAXAction
  14. {
  15. /** @var array */
  16. private $searchResults;
  17. /** @var int */
  18. private $limit;
  19. /** @var int */
  20. private $offset;
  21. /** @var int */
  22. private $requestedPage;
  23. /** @var string */
  24. private $searchTerm = '';
  25. /** @var CacheItemPoolInterface */
  26. private $cache;
  27. /** @var string */
  28. private $cacheKey;
  29. /** @var int */
  30. private $autoSuggestItemLength;
  31. /** @var array */
  32. private $pagination;
  33. private function display(): void
  34. {
  35. // set variables
  36. $this->requestedPage = 1;
  37. $this->limit = (int) $this->get('fork.settings')->get('Search', 'autosuggest_num_items', 10);
  38. $this->offset = ($this->requestedPage * $this->limit) - $this->limit;
  39. $this->cache = $this->get('cache.search');
  40. $this->cacheKey = implode(
  41. '_',
  42. [$this->getModule(), LANGUAGE, md5($this->searchTerm), $this->offset, $this->limit]
  43. );
  44. if (!$this->getCachedData()) {
  45. // no valid cache so we get fresh data
  46. $this->getRealData();
  47. }
  48. $this->parse();
  49. }
  50. public function execute(): void
  51. {
  52. parent::execute();
  53. $this->validateForm();
  54. if ($this->searchTerm === '') {
  55. $this->output(Response::HTTP_BAD_REQUEST, null, 'term-parameter is missing.');
  56. return;
  57. }
  58. $this->display();
  59. }
  60. private function getCachedData(): bool
  61. {
  62. if (!$this->searchTerm || $this->getContainer()->getParameter('kernel.debug')) {
  63. return false;
  64. }
  65. $cacheItem = $this->cache->getItem($this->cacheKey);
  66. if (!$cacheItem->isHit()) {
  67. return false;
  68. }
  69. ['pagination' => $this->pagination, 'items' => $this->searchResults] = $cacheItem->get();
  70. return true;
  71. }
  72. private function getRealData(): void
  73. {
  74. if (!$this->searchTerm) {
  75. return;
  76. }
  77. $this->searchResults = FrontendSearchModel::search($this->searchTerm, $this->limit, $this->offset);
  78. // populate count fields in pagination
  79. // this is done after actual search because some items might be
  80. // activated/deactivated (getTotal only does rough checking)
  81. $numberOfItems = FrontendSearchModel::getTotal($this->searchTerm);
  82. $this->pagination = [
  83. 'url' => FrontendNavigation::getUrlForBlock('Search') . '?form=search&q=' . $this->searchTerm,
  84. 'limit' => $this->limit,
  85. 'offset' => $this->offset,
  86. 'requested_page' => $this->requestedPage,
  87. 'num_items' => FrontendSearchModel::getTotal($this->searchTerm),
  88. 'num_pages' => (int) ceil($numberOfItems / $this->limit),
  89. ];
  90. // num pages is always equal to at least 1
  91. if ($this->pagination['num_pages'] === 0) {
  92. $this->pagination['num_pages'] = 1;
  93. }
  94. // Don't save the result in the cache when debug is enabled
  95. if ($this->getContainer()->getParameter('kernel.debug')) {
  96. return;
  97. }
  98. $cacheItem = $this->cache->getItem($this->cacheKey);
  99. $cacheItem->expiresAfter(new DateInterval('PT1H'));
  100. $cacheItem->set(['pagination' => $this->pagination, 'items' => $this->searchResults]);
  101. $this->cache->save($cacheItem);
  102. }
  103. public function parse(): void
  104. {
  105. // more matches to be found than allowed?
  106. if ($this->pagination['num_items'] > count($this->searchResults)) {
  107. // remove last result (to add this reference)
  108. array_pop($this->searchResults);
  109. // add reference to full search results page
  110. $this->searchResults[] = [
  111. 'title' => FL::lbl('More'),
  112. 'text' => FL::msg('MoreResults'),
  113. 'full_url' => FrontendNavigation::getUrlForBlock($this->getModule())
  114. . '?form=search&q=' . $this->searchTerm,
  115. ];
  116. }
  117. $charset = $this->getContainer()->getParameter('kernel.charset');
  118. $this->output(
  119. Response::HTTP_OK,
  120. array_map(
  121. function (array $searchResult) use ($charset) {
  122. if (empty($searchResult['text'])
  123. || mb_strlen($searchResult['text']) <= $this->autoSuggestItemLength) {
  124. return $searchResult;
  125. }
  126. $searchResult['text'] = mb_substr(
  127. strip_tags($searchResult['text']),
  128. 0,
  129. $this->autoSuggestItemLength,
  130. $charset
  131. ) . '…';
  132. return $searchResult;
  133. },
  134. $this->searchResults
  135. )
  136. );
  137. }
  138. private function validateForm(): void
  139. {
  140. // set values
  141. $charset = $this->getContainer()->getParameter('kernel.charset');
  142. $searchTerm = $this->getRequest()->request->get('term', '');
  143. $this->searchTerm = ($charset === 'utf-8')
  144. ? \SpoonFilter::htmlspecialchars($searchTerm) : \SpoonFilter::htmlentities($searchTerm);
  145. $this->autoSuggestItemLength = $this->getRequest()->request->getInt('length', 50);
  146. }
  147. }