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