PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

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

http://github.com/forkcms/forkcms
PHP | 317 lines | 210 code | 57 blank | 50 comment | 35 complexity | ae0da1ddac4772de2702fb77f09c1ed4 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\Engine\Exception as FrontendException;
  7. use Frontend\Core\Engine\Navigation as FrontendNavigation;
  8. use Frontend\Core\Engine\Theme;
  9. use Frontend\Core\Engine\TwigTemplate;
  10. use Frontend\Modules\Search\Engine\Model as FrontendSearchModel;
  11. use Symfony\Component\HttpFoundation\Response;
  12. /**
  13. * This is the live suggest-action, it will output a list of results for a certain search
  14. */
  15. class Livesuggest extends FrontendBaseAJAXAction
  16. {
  17. /** @var array */
  18. private $searchResults;
  19. /** @var int */
  20. private $limit;
  21. /** @var int */
  22. private $offset;
  23. /** @var int */
  24. private $requestedPage;
  25. /** @var array */
  26. private $pagination;
  27. /** @var string */
  28. private $searchTerm = '';
  29. /** @var CacheItemPoolInterface */
  30. private $cache;
  31. /** @var string */
  32. private $cacheKey;
  33. /** @var TwigTemplate */
  34. private $template;
  35. private function display(): void
  36. {
  37. $this->requestedPage = 1;
  38. $this->limit = (int) $this->get('fork.settings')->get('Search', 'overview_num_items', 20);
  39. $this->offset = ($this->requestedPage * $this->limit) - $this->limit;
  40. $this->cache = $this->get('cache.search');
  41. $this->cacheKey = implode(
  42. '_',
  43. [$this->getModule(), LANGUAGE, md5($this->searchTerm), $this->offset, $this->limit]
  44. );
  45. if (!$this->getCachedData()) {
  46. // no valid cache so we get fresh data
  47. $this->getRealData();
  48. }
  49. $this->parse();
  50. $this->output(
  51. Response::HTTP_OK,
  52. $this->template->render(
  53. Theme::getPath(FRONTEND_MODULES_PATH . '/Search/Layout/Templates/Results.html.twig'),
  54. $this->template->getAssignedVariables()
  55. )
  56. );
  57. }
  58. public function execute(): void
  59. {
  60. parent::execute();
  61. $this->validateForm();
  62. if ($this->searchTerm === '') {
  63. $this->output(Response::HTTP_BAD_REQUEST, null, 'term-parameter is missing.');
  64. return;
  65. }
  66. $this->display();
  67. }
  68. private function getCachedData(): bool
  69. {
  70. if (!$this->searchTerm || $this->getContainer()->getParameter('kernel.debug')) {
  71. return false;
  72. }
  73. $cacheItem = $this->cache->getItem($this->cacheKey);
  74. if (!$cacheItem->isHit()) {
  75. return false;
  76. }
  77. ['pagination' => $this->pagination, 'items' => $this->searchResults] = $cacheItem->get();
  78. return true;
  79. }
  80. private function getRealData(): void
  81. {
  82. if (!$this->searchTerm) {
  83. return;
  84. }
  85. $this->searchResults = FrontendSearchModel::search($this->searchTerm, $this->limit, $this->offset);
  86. // populate count fields in pagination
  87. // this is done after actual search because some items might be
  88. // activated/deactivated (getTotal only does rough checking)
  89. $numberOfItems = FrontendSearchModel::getTotal($this->searchTerm);
  90. $this->pagination = [
  91. 'url' => FrontendNavigation::getUrlForBlock('Search') . '?form=search&q=' . $this->searchTerm,
  92. 'limit' => $this->limit,
  93. 'offset' => $this->offset,
  94. 'requested_page' => $this->requestedPage,
  95. 'num_items' => FrontendSearchModel::getTotal($this->searchTerm),
  96. 'num_pages' => (int) ceil($numberOfItems / $this->limit)
  97. ];
  98. // num pages is always equal to at least 1
  99. if ($this->pagination['num_pages'] === 0) {
  100. $this->pagination['num_pages'] = 1;
  101. }
  102. // Don't save the result in the cache when debug is enabled
  103. if ($this->getContainer()->getParameter('kernel.debug')) {
  104. return;
  105. }
  106. $cacheItem = $this->cache->getItem($this->cacheKey);
  107. $cacheItem->expiresAfter(new DateInterval('PT1H'));
  108. $cacheItem->set(['pagination' => $this->pagination, 'items' => $this->searchResults]);
  109. $this->cache->save($cacheItem);
  110. }
  111. private function parse(): void
  112. {
  113. $this->template = $this->get('templating');
  114. if (!$this->searchTerm) {
  115. return;
  116. }
  117. $this->template->assign('searchResults', $this->searchResults);
  118. $this->template->assign('searchTerm', $this->searchTerm);
  119. $this->parsePagination();
  120. }
  121. private function parsePagination(): void
  122. {
  123. // init var
  124. $pagination = [];
  125. $showFirstPages = false;
  126. $showLastPages = false;
  127. $useQuestionMark = true;
  128. // validate pagination array
  129. switch (true) {
  130. case (!isset($this->pagination['limit'])):
  131. throw new FrontendException('no limit in the pagination-property.');
  132. case (!isset($this->pagination['offset'])):
  133. throw new FrontendException('no offset in the pagination-property.');
  134. case (!isset($this->pagination['requested_page'])):
  135. throw new FrontendException('no requested_page available in the pagination-property.');
  136. case (!isset($this->pagination['num_items'])):
  137. throw new FrontendException('no num_items available in the pagination-property.');
  138. case (!isset($this->pagination['num_pages'])):
  139. throw new FrontendException('no num_pages available in the pagination-property.');
  140. case (!isset($this->pagination['url'])):
  141. throw new FrontendException('no url available in the pagination-property.');
  142. }
  143. // should we use a questionmark or an ampersand
  144. if (mb_strpos($this->pagination['url'], '?') !== false) {
  145. $useQuestionMark = false;
  146. }
  147. // no pagination needed
  148. if ($this->pagination['num_pages'] < 1) {
  149. return;
  150. }
  151. // populate count fields
  152. $pagination['num_pages'] = $this->pagination['num_pages'];
  153. $pagination['current_page'] = $this->pagination['requested_page'];
  154. // as long as we are below page 5 we should show all pages starting from 1
  155. if ($this->pagination['requested_page'] < 6) {
  156. $pagesStart = 1;
  157. $pagesEnd = ($this->pagination['num_pages'] >= 6) ? 6 : $this->pagination['num_pages'];
  158. // show last pages
  159. if ($this->pagination['num_pages'] > 5) {
  160. $showLastPages = true;
  161. }
  162. } elseif ($this->pagination['requested_page'] >= ($this->pagination['num_pages'] - 4)) {
  163. // as long as we are 5 pages from the end we should show all pages till the end
  164. $pagesStart = ($this->pagination['num_pages'] - 5);
  165. $pagesEnd = $this->pagination['num_pages'];
  166. // show first pages
  167. if ($this->pagination['num_pages'] > 5) {
  168. $showFirstPages = true;
  169. }
  170. } else {
  171. // page 7
  172. $pagesStart = $this->pagination['requested_page'] - 2;
  173. $pagesEnd = $this->pagination['requested_page'] + 2;
  174. $showFirstPages = true;
  175. $showLastPages = true;
  176. }
  177. // show previous
  178. if ($this->pagination['requested_page'] > 1) {
  179. // build URL
  180. if ($useQuestionMark) {
  181. $url = $this->pagination['url'] . '?page=' . ($this->pagination['requested_page'] - 1);
  182. } else {
  183. $url = $this->pagination['url'] . '&page=' . ($this->pagination['requested_page'] - 1);
  184. }
  185. // set
  186. $pagination['show_previous'] = true;
  187. $pagination['previous_url'] = $url;
  188. }
  189. // show first pages?
  190. if ($showFirstPages) {
  191. // init var
  192. $pagesFirstStart = 1;
  193. $pagesFirstEnd = 1;
  194. // loop pages
  195. for ($i = $pagesFirstStart; $i <= $pagesFirstEnd; ++$i) {
  196. // build URL
  197. if ($useQuestionMark) {
  198. $url = $this->pagination['url'] . '?page=' . $i;
  199. } else {
  200. $url = $this->pagination['url'] . '&page=' . $i;
  201. }
  202. // add
  203. $pagination['first'][] = ['url' => $url, 'label' => $i];
  204. }
  205. }
  206. // build array
  207. for ($i = $pagesStart; $i <= $pagesEnd; ++$i) {
  208. // init var
  209. $current = ($i == $this->pagination['requested_page']);
  210. // build URL
  211. if ($useQuestionMark) {
  212. $url = $this->pagination['url'] . '?page=' . $i;
  213. } else {
  214. $url = $this->pagination['url'] . '&page=' . $i;
  215. }
  216. // add
  217. $pagination['pages'][] = ['url' => $url, 'label' => $i, 'current' => $current];
  218. }
  219. // show last pages?
  220. if ($showLastPages) {
  221. // init var
  222. $pagesLastStart = $this->pagination['num_pages'];
  223. $pagesLastEnd = $this->pagination['num_pages'];
  224. // loop pages
  225. for ($i = $pagesLastStart; $i <= $pagesLastEnd; ++$i) {
  226. // build URL
  227. if ($useQuestionMark) {
  228. $url = $this->pagination['url'] . '?page=' . $i;
  229. } else {
  230. $url = $this->pagination['url'] . '&page=' . $i;
  231. }
  232. // add
  233. $pagination['last'][] = ['url' => $url, 'label' => $i];
  234. }
  235. }
  236. // show next
  237. if ($this->pagination['requested_page'] < $this->pagination['num_pages']) {
  238. // build URL
  239. if ($useQuestionMark) {
  240. $url = $this->pagination['url'] . '?page=' . ($this->pagination['requested_page'] + 1);
  241. } else {
  242. $url = $this->pagination['url'] . '&page=' . ($this->pagination['requested_page'] + 1);
  243. }
  244. // set
  245. $pagination['show_next'] = true;
  246. $pagination['next_url'] = $url;
  247. }
  248. // multiple pages
  249. $pagination['multiple_pages'] = ($pagination['num_pages'] == 1) ? false : true;
  250. // assign pagination
  251. $this->template->assign('pagination', $pagination);
  252. }
  253. private function validateForm(): void
  254. {
  255. $charset = $this->getContainer()->getParameter('kernel.charset');
  256. $searchTerm = $this->getRequest()->request->get('term', '');
  257. $this->searchTerm = ($charset === 'utf-8')
  258. ? \SpoonFilter::htmlspecialchars($searchTerm) : \SpoonFilter::htmlentities($searchTerm);
  259. }
  260. }