PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Frontend/Modules/Search/Actions/Index.php

http://github.com/forkcms/forkcms
PHP | 222 lines | 160 code | 44 blank | 18 comment | 16 complexity | 049ddf9b10bec9f49945421515db9b1a 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\Actions;
  3. use DateInterval;
  4. use Psr\Cache\CacheItemPoolInterface;
  5. use Frontend\Core\Engine\Base\Block as FrontendBaseBlock;
  6. use Frontend\Core\Engine\Form as FrontendForm;
  7. use Frontend\Core\Language\Language as FL;
  8. use Frontend\Core\Engine\Model as FrontendModel;
  9. use Frontend\Core\Engine\Navigation as FrontendNavigation;
  10. use Frontend\Modules\Search\Engine\Model as FrontendSearchModel;
  11. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  12. class Index extends FrontendBaseBlock
  13. {
  14. /** @var FrontendForm */
  15. private $form;
  16. /** @var array */
  17. private $searchResults;
  18. /** @var int */
  19. private $limit;
  20. /** @var int */
  21. private $offset;
  22. /** @var int */
  23. private $requestedPage;
  24. /** @var string */
  25. private $searchTerm = '';
  26. /** @var CacheItemPoolInterface */
  27. private $cache;
  28. /** @var string */
  29. private $cacheKey;
  30. private function display(): void
  31. {
  32. $this->requestedPage = $this->url->getParameter('page', 'int', 1);
  33. $this->limit = $this->get('fork.settings')->get('Search', 'overview_num_items', 20);
  34. $this->offset = ($this->requestedPage * $this->limit) - $this->limit;
  35. $this->cache = $this->get('cache.search');
  36. $this->cacheKey = implode(
  37. '_',
  38. [$this->getModule(), LANGUAGE, md5($this->searchTerm), $this->offset, $this->limit]
  39. );
  40. if (!$this->getCachedData()) {
  41. // no valid cache so we get fresh data
  42. $this->getRealData();
  43. }
  44. $this->parse();
  45. }
  46. public function execute(): void
  47. {
  48. parent::execute();
  49. $this->loadTemplate();
  50. $this->buildForm();
  51. $this->validateForm();
  52. $this->display();
  53. $this->saveStatistics();
  54. }
  55. private function getCachedData(): bool
  56. {
  57. if (!$this->searchTerm || $this->getContainer()->getParameter('kernel.debug')) {
  58. return false;
  59. }
  60. $cacheItem = $this->cache->getItem($this->cacheKey);
  61. if (!$cacheItem->isHit()) {
  62. return false;
  63. }
  64. ['pagination' => $this->pagination, 'items' => $this->searchResults] = $cacheItem->get();
  65. return true;
  66. }
  67. private function getRealData(): void
  68. {
  69. if (!$this->searchTerm) {
  70. return;
  71. }
  72. $this->searchResults = FrontendSearchModel::search($this->searchTerm, $this->limit, $this->offset);
  73. // populate count fields in pagination
  74. // this is done after actual search because some items might be
  75. // activated/deactivated (getTotal only does rough checking)
  76. $numberOfItems = FrontendSearchModel::getTotal($this->searchTerm);
  77. $this->pagination = [
  78. 'url' => FrontendNavigation::getUrlForBlock('Search') . '?form=search&q=' . $this->searchTerm,
  79. 'limit' => $this->limit,
  80. 'offset' => $this->offset,
  81. 'requested_page' => $this->requestedPage,
  82. 'num_items' => FrontendSearchModel::getTotal($this->searchTerm),
  83. 'num_pages' => (int) ceil($numberOfItems / $this->limit)
  84. ];
  85. // num pages is always equal to at least 1
  86. if ($this->pagination['num_pages'] === 0) {
  87. $this->pagination['num_pages'] = 1;
  88. }
  89. if ($this->requestedPage < 1 || $this->requestedPage > $this->pagination['num_pages']) {
  90. throw new NotFoundHttpException();
  91. }
  92. // Don't save the result in the cache when debug is enabled
  93. if ($this->getContainer()->getParameter('kernel.debug')) {
  94. return;
  95. }
  96. $cacheItem = $this->cache->getItem($this->cacheKey);
  97. $cacheItem->expiresAfter(new DateInterval('PT1H'));
  98. $cacheItem->set(['pagination' => $this->pagination, 'items' => $this->searchResults]);
  99. $this->cache->save($cacheItem);
  100. }
  101. private function buildForm(): void
  102. {
  103. $this->form = new FrontendForm('search', null, 'get', null, false);
  104. $query = $this->getQuery();
  105. $this->form->addText('q', $query)->setAttributes(
  106. [
  107. 'data-role' => 'fork-search-field',
  108. 'data-autocomplete' => 'enabled',
  109. 'data-live-suggest' => 'enabled',
  110. ]
  111. );
  112. $this->header->setCanonicalUrl($this->getCanonicalUrl($query));
  113. }
  114. private function getQuery(): string
  115. {
  116. if ($this->getRequest()->query->has('q')) {
  117. return $this->getRequest()->query->get('q', '');
  118. }
  119. // search query was submitted by our search widget
  120. $query = $this->getRequest()->query->get('q_widget', '');
  121. // set $_GET variable to keep SpoonForm happy
  122. // should be refactored out when Symfony form are implemented here
  123. $_GET['q'] = $query;
  124. return $query;
  125. }
  126. private function getCanonicalUrl(string $query): string
  127. {
  128. $canonicalUrl = SITE_URL . FrontendNavigation::getUrlForBlock('Search');
  129. if ($query === '') {
  130. return $canonicalUrl;
  131. }
  132. return $canonicalUrl . '?q=' . \SpoonFilter::htmlspecialchars($query);
  133. }
  134. private function parse(): void
  135. {
  136. $this->addJS('/js/vendors/typeahead.bundle.min.js', true, false);
  137. $this->addCSS('Search.css');
  138. $this->form->parse($this->template);
  139. if (!$this->searchTerm) {
  140. return;
  141. }
  142. $this->template->assign('searchResults', $this->searchResults);
  143. $this->template->assign('searchTerm', $this->searchTerm);
  144. $this->parsePagination();
  145. }
  146. private function saveStatistics(): void
  147. {
  148. if (!$this->searchTerm) {
  149. return;
  150. }
  151. $previousTerm = FrontendModel::getSession()->get('searchTerm', '');
  152. FrontendModel::getSession()->set('searchTerm', '');
  153. // don't save the search term in the database if it is the same as the last time
  154. if ($previousTerm !== $this->searchTerm) {
  155. FrontendSearchModel::save(
  156. [
  157. 'term' => $this->searchTerm,
  158. 'language' => LANGUAGE,
  159. 'time' => FrontendModel::getUTCDate(),
  160. 'data' => serialize(['server' => $_SERVER]),
  161. 'num_results' => $this->pagination['num_items'],
  162. ]
  163. );
  164. }
  165. FrontendModel::getSession()->set('searchTerm', $this->searchTerm);
  166. }
  167. private function validateForm(): void
  168. {
  169. if (!$this->form->isSubmitted()) {
  170. return;
  171. }
  172. $this->form->cleanupFields();
  173. $this->form->getField('q')->isFilled(FL::err('TermIsRequired'));
  174. if ($this->form->isCorrect()) {
  175. $this->searchTerm = $this->form->getField('q')->getValue();
  176. }
  177. }
  178. }