1
|
|
|
<?php |
2
|
|
|
namespace ApacheSolrForTypo3\Solr; |
3
|
|
|
|
4
|
|
|
/*************************************************************** |
5
|
|
|
* Copyright notice |
6
|
|
|
* |
7
|
|
|
* (c) 2009-2015 Ingo Renner <[email protected]> |
8
|
|
|
* All rights reserved |
9
|
|
|
* |
10
|
|
|
* This script is part of the TYPO3 project. The TYPO3 project is |
11
|
|
|
* free software; you can redistribute it and/or modify |
12
|
|
|
* it under the terms of the GNU General Public License as published by |
13
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
14
|
|
|
* (at your option) any later version. |
15
|
|
|
* |
16
|
|
|
* The GNU General Public License can be found at |
17
|
|
|
* http://www.gnu.org/copyleft/gpl.html. |
18
|
|
|
* |
19
|
|
|
* This script is distributed in the hope that it will be useful, |
20
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
21
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22
|
|
|
* GNU General Public License for more details. |
23
|
|
|
* |
24
|
|
|
* This copyright notice MUST APPEAR in all copies of the script! |
25
|
|
|
***************************************************************/ |
26
|
|
|
|
27
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService; |
28
|
|
|
use ApacheSolrForTypo3\Solr\FieldProcessor\PageUidToHierarchy; |
29
|
|
|
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration; |
30
|
|
|
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager; |
31
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* A Solr search query |
35
|
|
|
* |
36
|
|
|
* @author Ingo Renner <[email protected]> |
37
|
|
|
*/ |
38
|
|
|
class Query |
39
|
|
|
{ |
40
|
|
|
|
41
|
|
|
// FIXME extract link building from the query, it's not the query's domain |
42
|
|
|
|
43
|
|
|
const SORT_ASC = 'ASC'; |
44
|
|
|
const SORT_DESC = 'DESC'; |
45
|
|
|
|
46
|
|
|
const OPERATOR_AND = 'AND'; |
47
|
|
|
const OPERATOR_OR = 'OR'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Used to identify the queries. |
51
|
|
|
* |
52
|
|
|
* @var int |
53
|
|
|
*/ |
54
|
|
|
protected static $idCount = 0; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var int |
58
|
|
|
*/ |
59
|
|
|
protected $id; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var TypoScriptConfiguration |
63
|
|
|
*/ |
64
|
|
|
protected $solrConfiguration; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @var string |
68
|
|
|
*/ |
69
|
|
|
protected $keywords; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var string |
73
|
|
|
*/ |
74
|
|
|
protected $keywordsRaw; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var array |
78
|
|
|
*/ |
79
|
|
|
protected $filters = []; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var string |
83
|
|
|
*/ |
84
|
|
|
protected $sorting; |
85
|
|
|
|
86
|
|
|
// TODO check usage of these two variants, especially the check for $rawQueryString in getQueryString() |
87
|
|
|
/** |
88
|
|
|
* @var |
89
|
|
|
*/ |
90
|
|
|
protected $queryString; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @var array |
94
|
|
|
*/ |
95
|
|
|
protected $queryParameters = []; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @var int |
99
|
|
|
*/ |
100
|
|
|
protected $resultsPerPage; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @var int |
104
|
|
|
*/ |
105
|
|
|
protected $page; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @var int |
109
|
|
|
*/ |
110
|
|
|
protected $linkTargetPageId; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Holds the query fields with their associated boosts. The key represents |
114
|
|
|
* the field name, value represents the field's boost. These are the fields |
115
|
|
|
* that will actually be searched. |
116
|
|
|
* |
117
|
|
|
* Used in Solr's qf parameter |
118
|
|
|
* |
119
|
|
|
* @var array |
120
|
|
|
* @see http://wiki.apache.org/solr/DisMaxQParserPlugin#qf_.28Query_Fields.29 |
121
|
|
|
*/ |
122
|
|
|
protected $queryFields = []; |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* List of fields that will be returned in the result documents. |
126
|
|
|
* |
127
|
|
|
* used in Solr's fl parameter |
128
|
|
|
* |
129
|
|
|
* @var array |
130
|
|
|
* @see http://wiki.apache.org/solr/CommonQueryParameters#fl |
131
|
|
|
*/ |
132
|
|
|
protected $fieldList = []; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @var array |
136
|
|
|
*/ |
137
|
|
|
protected $filterFields; |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @var array |
141
|
|
|
* @deprecated since 6.1 will be removed in 7.0 |
142
|
|
|
*/ |
143
|
|
|
protected $sortingFields = []; |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @var bool |
147
|
|
|
*/ |
148
|
|
|
private $rawQueryString = false; |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* The field by which the result will be collapsed |
152
|
|
|
* @var string |
153
|
|
|
*/ |
154
|
|
|
protected $variantField = 'variantId'; |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @var SiteHashService |
158
|
|
|
*/ |
159
|
|
|
protected $siteHashService = null; |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager |
163
|
|
|
*/ |
164
|
|
|
protected $logger = null; |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Query constructor. |
168
|
|
|
* @param string $keywords |
169
|
|
|
* @param TypoScriptConfiguration $solrConfiguration |
170
|
|
|
* @param SiteHashService|null $siteHashService |
171
|
|
|
*/ |
172
|
132 |
|
public function __construct($keywords, $solrConfiguration = null, SiteHashService $siteHashService = null) |
173
|
|
|
{ |
174
|
132 |
|
$keywords = (string)$keywords; |
175
|
|
|
|
176
|
132 |
|
$this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__); |
177
|
132 |
|
$this->solrConfiguration = is_null($solrConfiguration) ? Util::getSolrConfiguration() : $solrConfiguration; |
178
|
132 |
|
$this->siteHashService = is_null($siteHashService) ? GeneralUtility::makeInstance(SiteHashService::class) : $siteHashService; |
179
|
|
|
|
180
|
132 |
|
$this->setKeywords($keywords); |
181
|
132 |
|
$this->sorting = ''; |
182
|
|
|
|
183
|
|
|
// What fields to search |
184
|
132 |
|
$queryFields = $this->solrConfiguration->getSearchQueryQueryFields(); |
185
|
132 |
|
if ($queryFields != '') { |
186
|
27 |
|
$this->setQueryFieldsFromString($queryFields); |
187
|
27 |
|
} |
188
|
|
|
|
189
|
|
|
// What fields to return from Solr |
190
|
132 |
|
$this->fieldList = $this->solrConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']); |
191
|
132 |
|
$this->linkTargetPageId = $this->solrConfiguration->getSearchTargetPage(); |
192
|
|
|
|
193
|
132 |
|
$this->initializeQuery(); |
194
|
|
|
|
195
|
132 |
|
$this->id = ++self::$idCount; |
196
|
132 |
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @return void |
200
|
|
|
*/ |
201
|
131 |
|
protected function initializeQuery() |
202
|
|
|
{ |
203
|
131 |
|
$this->initializeCollapsingFromConfiguration(); |
204
|
131 |
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Takes a string of comma separated query fields and _overwrites_ the |
208
|
|
|
* currently set query fields. Boost can also be specified in through the |
209
|
|
|
* given string. |
210
|
|
|
* |
211
|
|
|
* Example: "title^5, subtitle^2, content, author^0.5" |
212
|
|
|
* This sets the query fields to title with a boost of 5.0, subtitle with |
213
|
|
|
* a boost of 2.0, content with a default boost of 1.0 and the author field |
214
|
|
|
* with a boost of 0.5 |
215
|
|
|
* |
216
|
|
|
* @param string $queryFields A string defining which fields to query and their associated boosts |
217
|
|
|
* @return void |
218
|
|
|
*/ |
219
|
28 |
|
public function setQueryFieldsFromString($queryFields) |
220
|
|
|
{ |
221
|
28 |
|
$fields = GeneralUtility::trimExplode(',', $queryFields, true); |
222
|
|
|
|
223
|
28 |
|
foreach ($fields as $field) { |
224
|
28 |
|
$fieldNameAndBoost = explode('^', $field); |
225
|
|
|
|
226
|
28 |
|
$boost = 1.0; |
227
|
28 |
|
if (isset($fieldNameAndBoost[1])) { |
228
|
28 |
|
$boost = floatval($fieldNameAndBoost[1]); |
229
|
28 |
|
} |
230
|
|
|
|
231
|
28 |
|
$this->setQueryField($fieldNameAndBoost[0], $boost); |
232
|
28 |
|
} |
233
|
28 |
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Sets a query field and its boost. If the field does not exist yet, it |
237
|
|
|
* gets added. Boost is optional, if left out a default boost of 1.0 is |
238
|
|
|
* applied. |
239
|
|
|
* |
240
|
|
|
* @param string $fieldName The field's name |
241
|
|
|
* @param float $boost Optional field boost, defaults to 1.0 |
242
|
|
|
* @return void |
243
|
|
|
*/ |
244
|
28 |
|
public function setQueryField($fieldName, $boost = 1.0) |
245
|
|
|
{ |
246
|
28 |
|
$this->queryFields[$fieldName] = (float)$boost; |
247
|
28 |
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* magic implementation for clone(), makes sure that the id counter is |
251
|
|
|
* incremented |
252
|
|
|
* |
253
|
|
|
* @return void |
254
|
|
|
*/ |
255
|
5 |
|
public function __clone() |
256
|
|
|
{ |
257
|
5 |
|
$this->id = ++self::$idCount; |
258
|
5 |
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* returns a string representation of the query |
262
|
|
|
* |
263
|
|
|
* @return string the string representation of the query |
264
|
|
|
*/ |
265
|
8 |
|
public function __toString() |
266
|
|
|
{ |
267
|
8 |
|
return $this->getQueryString(); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Builds the query string which is then used for Solr's q parameters |
272
|
|
|
* |
273
|
|
|
* @return string Solr query string |
274
|
|
|
*/ |
275
|
38 |
|
public function getQueryString() |
276
|
|
|
{ |
277
|
38 |
|
if (!$this->rawQueryString) { |
278
|
35 |
|
$this->buildQueryString(); |
279
|
35 |
|
} |
280
|
|
|
|
281
|
38 |
|
return $this->queryString; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Sets the query string without any escaping. |
286
|
|
|
* |
287
|
|
|
* Be cautious with this function! |
288
|
|
|
* TODO remove this method as it basically just sets the q parameter / keywords |
289
|
|
|
* |
290
|
|
|
* @param string $queryString The raw query string. |
291
|
|
|
*/ |
292
|
4 |
|
public function setQueryString($queryString) |
293
|
|
|
{ |
294
|
4 |
|
$this->queryString = $queryString; |
295
|
4 |
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Creates the string that is later used as the q parameter in the solr query |
299
|
|
|
* |
300
|
|
|
* @return void |
301
|
|
|
*/ |
302
|
35 |
|
protected function buildQueryString() |
303
|
|
|
{ |
304
|
|
|
// very simple for now |
305
|
35 |
|
$this->queryString = $this->keywords; |
306
|
35 |
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Sets whether a raw query sting should be used, that is, whether the query |
310
|
|
|
* string should be escaped or not. |
311
|
|
|
* |
312
|
|
|
* @param bool $useRawQueryString TRUE to use raw queries (like Lucene Query Language) or FALSE for regular, escaped queries |
313
|
|
|
*/ |
314
|
4 |
|
public function useRawQueryString($useRawQueryString) |
315
|
|
|
{ |
316
|
4 |
|
$this->rawQueryString = (boolean)$useRawQueryString; |
317
|
4 |
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Returns the query's ID. |
321
|
|
|
* |
322
|
|
|
* @return int The query's ID. |
323
|
|
|
*/ |
324
|
|
|
public function getId() |
325
|
|
|
{ |
326
|
|
|
return $this->id; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Quote and escape search strings |
331
|
|
|
* |
332
|
|
|
* @param string $string String to escape |
333
|
|
|
* @return string The escaped/quoted string |
334
|
|
|
*/ |
335
|
132 |
|
public function escape($string) |
336
|
|
|
{ |
337
|
|
|
// when we have a numeric string only, nothing needs to be done |
338
|
132 |
|
if (is_numeric($string)) { |
339
|
1 |
|
return $string; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// when no whitespaces are in the query we can also just escape the special characters |
343
|
132 |
|
if (preg_match('/\W/', $string) != 1) { |
344
|
111 |
|
return $this->escapeSpecialCharacters($string); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
// when there are no quotes inside the query string we can also just escape the whole string |
348
|
37 |
|
$hasQuotes = strrpos($string, '"') !== false; |
349
|
37 |
|
if (!$hasQuotes) { |
350
|
30 |
|
return $this->escapeSpecialCharacters($string); |
351
|
|
|
} |
352
|
|
|
|
353
|
7 |
|
$result = $this->tokenizeByQuotesAndEscapeDependingOnContext($string); |
354
|
|
|
|
355
|
7 |
|
return $result; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* This method is used to escape the content in the query string surrounded by quotes |
360
|
|
|
* different then when it is not in a quoted context. |
361
|
|
|
* |
362
|
|
|
* @param string $string |
363
|
|
|
* @return string |
364
|
|
|
*/ |
365
|
7 |
|
protected function tokenizeByQuotesAndEscapeDependingOnContext($string) |
366
|
|
|
{ |
367
|
7 |
|
$result = ''; |
368
|
7 |
|
$quotesCount = substr_count($string, '"'); |
369
|
7 |
|
$isEvenAmountOfQuotes = $quotesCount % 2 === 0; |
370
|
|
|
|
371
|
|
|
// go over all quote segments and apply escapePhrase inside a quoted |
372
|
|
|
// context and escapeSpecialCharacters outside the quoted context. |
373
|
7 |
|
$segments = explode('"', $string); |
374
|
7 |
|
$segmentsIndex = 0; |
375
|
7 |
|
foreach ($segments as $segment) { |
376
|
7 |
|
$isInQuote = $segmentsIndex % 2 !== 0; |
377
|
7 |
|
$isLastQuote = $segmentsIndex === $quotesCount; |
378
|
|
|
|
379
|
7 |
|
if ($isLastQuote && !$isEvenAmountOfQuotes) { |
380
|
1 |
|
$result .= '\"'; |
381
|
1 |
|
} |
382
|
|
|
|
383
|
7 |
|
if ($isInQuote && !$isLastQuote) { |
384
|
6 |
|
$result .= $this->escapePhrase($segment); |
385
|
6 |
|
} else { |
386
|
7 |
|
$result .= $this->escapeSpecialCharacters($segment); |
387
|
|
|
} |
388
|
|
|
|
389
|
7 |
|
$segmentsIndex++; |
390
|
7 |
|
} |
391
|
|
|
|
392
|
7 |
|
return $result; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
// pagination |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Escapes a value meant to be contained in a phrase with characters with |
399
|
|
|
* special meanings in Lucene query syntax. |
400
|
|
|
* |
401
|
|
|
* @param string $value Unescaped - "dirty" - string |
402
|
|
|
* @return string Escaped - "clean" - string |
403
|
|
|
*/ |
404
|
6 |
|
protected function escapePhrase($value) |
405
|
|
|
{ |
406
|
6 |
|
$pattern = '/("|\\\)/'; |
407
|
6 |
|
$replace = '\\\$1'; |
408
|
|
|
|
409
|
6 |
|
return '"' . preg_replace($pattern, $replace, $value) . '"'; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Escapes characters with special meanings in Lucene query syntax. |
414
|
|
|
* |
415
|
|
|
* @param string $value Unescaped - "dirty" - string |
416
|
|
|
* @return string Escaped - "clean" - string |
417
|
|
|
*/ |
418
|
132 |
|
protected function escapeSpecialCharacters($value) |
419
|
|
|
{ |
420
|
|
|
// list taken from http://lucene.apache.org/core/4_4_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description |
421
|
|
|
// which mentions: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / |
422
|
|
|
// of which we escape: ( ) { } [ ] ^ " ~ : \ / |
423
|
|
|
// and explicitly don't escape: + - && || ! * ? |
424
|
132 |
|
$pattern = '/(\\(|\\)|\\{|\\}|\\[|\\]|\\^|"|~|\:|\\\\|\\/)/'; |
425
|
132 |
|
$replace = '\\\$1'; |
426
|
|
|
|
427
|
132 |
|
return preg_replace($pattern, $replace, $value); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Gets the currently showing page's number |
432
|
|
|
* |
433
|
|
|
* @return int page number currently showing |
434
|
|
|
*/ |
435
|
1 |
|
public function getPage() |
436
|
|
|
{ |
437
|
1 |
|
return $this->page; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Sets the page that should be shown |
442
|
|
|
* |
443
|
|
|
* @param int $page page number to show |
444
|
|
|
* @return void |
445
|
|
|
*/ |
446
|
1 |
|
public function setPage($page) |
447
|
|
|
{ |
448
|
1 |
|
$this->page = max(intval($page), 0); |
449
|
1 |
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Gets the index of the first result document we're showing |
453
|
|
|
* |
454
|
|
|
* @return int index of the currently first document showing |
455
|
|
|
*/ |
456
|
|
|
public function getStartIndex() |
457
|
|
|
{ |
458
|
|
|
return ($this->page - 1) * $this->resultsPerPage; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Gets the index of the last result document we're showing |
463
|
|
|
* |
464
|
|
|
* @return int index of the currently last document showing |
465
|
|
|
*/ |
466
|
|
|
public function getEndIndex() |
467
|
|
|
{ |
468
|
|
|
return $this->page * $this->resultsPerPage; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
// query elevation |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Activates and deactivates query elevation for the current query. |
475
|
|
|
* |
476
|
|
|
* @param bool $elevation True to enable query elevation (default), FALSE to disable query elevation. |
477
|
|
|
* @param bool $forceElevation Optionally force elevation so that the elevated documents are always on top regardless of sorting, default to TRUE. |
478
|
|
|
* @param bool $markElevatedResults Mark elevated results |
479
|
|
|
* @return void |
480
|
|
|
*/ |
481
|
26 |
|
public function setQueryElevation( |
482
|
|
|
$elevation = true, |
483
|
|
|
$forceElevation = true, |
484
|
|
|
$markElevatedResults = true |
485
|
|
|
) { |
486
|
26 |
|
if ($elevation) { |
487
|
22 |
|
$this->queryParameters['enableElevation'] = 'true'; |
488
|
22 |
|
$this->setForceElevation($forceElevation); |
489
|
22 |
|
if ($markElevatedResults) { |
490
|
22 |
|
$this->addReturnField('isElevated:[elevated]'); |
491
|
22 |
|
} |
492
|
22 |
|
} else { |
493
|
5 |
|
$this->queryParameters['enableElevation'] = 'false'; |
494
|
5 |
|
unset($this->queryParameters['forceElevation']); |
495
|
5 |
|
$this->removeReturnField('isElevated:[elevated]'); |
496
|
5 |
|
$this->removeReturnField('[elevated]'); // fallback |
497
|
|
|
} |
498
|
26 |
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Enables or disables the forceElevation query parameter. |
502
|
|
|
* |
503
|
|
|
* @param bool $forceElevation |
504
|
|
|
*/ |
505
|
22 |
|
protected function setForceElevation($forceElevation) |
506
|
|
|
{ |
507
|
22 |
|
if ($forceElevation) { |
508
|
21 |
|
$this->queryParameters['forceElevation'] = 'true'; |
509
|
21 |
|
} else { |
510
|
1 |
|
$this->queryParameters['forceElevation'] = 'false'; |
511
|
|
|
} |
512
|
22 |
|
} |
513
|
|
|
|
514
|
|
|
// collapsing |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Check whether collapsing is active |
518
|
|
|
* |
519
|
|
|
* @return bool |
520
|
|
|
*/ |
521
|
3 |
|
public function getIsCollapsing() |
522
|
|
|
{ |
523
|
3 |
|
return array_key_exists('collapsing', $this->filters); |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
/** |
527
|
|
|
* @param string $fieldName |
528
|
|
|
*/ |
529
|
3 |
|
public function setVariantField($fieldName) |
530
|
|
|
{ |
531
|
3 |
|
$this->variantField = $fieldName; |
532
|
3 |
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* @return string |
536
|
|
|
*/ |
537
|
1 |
|
public function getVariantField() |
538
|
|
|
{ |
539
|
1 |
|
return $this->variantField; |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* @param bool $collapsing |
544
|
|
|
*/ |
545
|
4 |
|
public function setCollapsing($collapsing = true) |
546
|
|
|
{ |
547
|
4 |
|
if ($collapsing) { |
548
|
4 |
|
$this->filters['collapsing'] = '{!collapse field=' . $this->variantField . '}'; |
549
|
4 |
|
if ($this->solrConfiguration->getSearchVariantsExpand()) { |
550
|
2 |
|
$this->queryParameters['expand'] = 'true'; |
551
|
2 |
|
$this->queryParameters['expand.rows'] = $this->solrConfiguration->getSearchVariantsLimit(); |
552
|
2 |
|
} |
553
|
4 |
|
} else { |
554
|
1 |
|
unset($this->filters['collapsing']); |
555
|
1 |
|
unset($this->queryParameters['expand']); |
556
|
1 |
|
unset($this->queryParameters['expand.rows']); |
557
|
|
|
} |
558
|
4 |
|
} |
559
|
|
|
|
560
|
|
|
// grouping |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Adds a field to the list of fields to return. Also checks whether * is |
564
|
|
|
* set for the fields, if so it's removed from the field list. |
565
|
|
|
* |
566
|
|
|
* @param string $fieldName Name of a field to return in the result documents |
567
|
|
|
*/ |
568
|
23 |
|
public function addReturnField($fieldName) |
569
|
|
|
{ |
570
|
23 |
|
if (strpos($fieldName, '[') === false |
571
|
23 |
|
&& strpos($fieldName, ']') === false |
572
|
23 |
|
&& in_array('*', $this->fieldList) |
573
|
23 |
|
) { |
574
|
1 |
|
$this->fieldList = array_diff($this->fieldList, ['*']); |
575
|
1 |
|
} |
576
|
|
|
|
577
|
23 |
|
$this->fieldList[] = $fieldName; |
578
|
23 |
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Removes a field from the list of fields to return (fl parameter). |
582
|
|
|
* |
583
|
|
|
* @param string $fieldName Field to remove from the list of fields to return |
584
|
|
|
*/ |
585
|
6 |
|
public function removeReturnField($fieldName) |
586
|
|
|
{ |
587
|
6 |
|
$key = array_search($fieldName, $this->fieldList); |
588
|
|
|
|
589
|
6 |
|
if ($key !== false) { |
590
|
2 |
|
unset($this->fieldList[$key]); |
591
|
2 |
|
} |
592
|
6 |
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* Activates and deactivates grouping for the current query. |
596
|
|
|
* |
597
|
|
|
* @param bool $grouping TRUE to enable grouping, FALSE to disable grouping |
598
|
|
|
* @return void |
599
|
|
|
*/ |
600
|
2 |
|
public function setGrouping($grouping = true) |
601
|
|
|
{ |
602
|
2 |
|
if ($grouping) { |
603
|
1 |
|
$this->queryParameters['group'] = 'true'; |
604
|
1 |
|
$this->queryParameters['group.format'] = 'grouped'; |
605
|
1 |
|
$this->queryParameters['group.ngroups'] = 'true'; |
606
|
1 |
|
} else { |
607
|
1 |
|
foreach ($this->queryParameters as $key => $value) { |
608
|
|
|
// remove all group.* settings |
609
|
1 |
|
if (GeneralUtility::isFirstPartOfStr($key, 'group')) { |
610
|
1 |
|
unset($this->queryParameters[$key]); |
611
|
1 |
|
} |
612
|
1 |
|
} |
613
|
|
|
} |
614
|
2 |
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Sets the number of groups to return per group field or group query |
618
|
|
|
* |
619
|
|
|
* Internally uses the rows parameter. |
620
|
|
|
* |
621
|
|
|
* @param int $numberOfGroups Number of groups per group.field or group.query |
622
|
|
|
*/ |
623
|
1 |
|
public function setNumberOfGroups($numberOfGroups) |
624
|
|
|
{ |
625
|
1 |
|
$this->setResultsPerPage($numberOfGroups); |
626
|
1 |
|
} |
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* Gets the number of groups to return per group field or group query |
630
|
|
|
* |
631
|
|
|
* Internally uses the rows parameter. |
632
|
|
|
* |
633
|
|
|
* @return int Number of groups per group.field or group.query |
634
|
|
|
*/ |
635
|
1 |
|
public function getNumberOfGroups() |
636
|
|
|
{ |
637
|
1 |
|
return $this->getResultsPerPage(); |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
/** |
641
|
|
|
* Returns the number of results that should be shown per page |
642
|
|
|
* |
643
|
|
|
* @return int number of results to show per page |
644
|
|
|
*/ |
645
|
25 |
|
public function getResultsPerPage() |
646
|
|
|
{ |
647
|
25 |
|
return $this->resultsPerPage; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
/** |
651
|
|
|
* Sets the number of results that should be shown per page |
652
|
|
|
* |
653
|
|
|
* @param int $resultsPerPage Number of results to show per page |
654
|
|
|
* @return void |
655
|
|
|
*/ |
656
|
32 |
|
public function setResultsPerPage($resultsPerPage) |
657
|
|
|
{ |
658
|
32 |
|
$this->resultsPerPage = max(intval($resultsPerPage), 0); |
659
|
32 |
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Adds a field that should be used for grouping. |
663
|
|
|
* |
664
|
|
|
* @param string $fieldName Name of a field for grouping |
665
|
|
|
*/ |
666
|
1 |
|
public function addGroupField($fieldName) |
667
|
|
|
{ |
668
|
1 |
|
if (!isset($this->queryParameters['group.field'])) { |
669
|
1 |
|
$this->queryParameters['group.field'] = []; |
670
|
1 |
|
} |
671
|
|
|
|
672
|
1 |
|
$this->queryParameters['group.field'][] = $fieldName; |
673
|
1 |
|
} |
674
|
|
|
|
675
|
|
|
/** |
676
|
|
|
* Gets the fields set for grouping. |
677
|
|
|
* |
678
|
|
|
* @return array An array of fields set for grouping. |
679
|
|
|
*/ |
680
|
1 |
|
public function getGroupFields() |
681
|
|
|
{ |
682
|
1 |
|
return (array)$this->getQueryParameter('group.field', []); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
/** |
686
|
|
|
* Adds sorting configuration for grouping. |
687
|
|
|
* |
688
|
|
|
* @param string $sorting value of sorting configuration |
689
|
|
|
*/ |
690
|
1 |
|
public function addGroupSorting($sorting) |
691
|
|
|
{ |
692
|
1 |
|
if (!isset($this->queryParameters['group.sort'])) { |
693
|
1 |
|
$this->queryParameters['group.sort'] = []; |
694
|
1 |
|
} |
695
|
1 |
|
$this->queryParameters['group.sort'][] = $sorting; |
696
|
1 |
|
} |
697
|
|
|
|
698
|
|
|
/** |
699
|
|
|
* Gets the sorting set for grouping. |
700
|
|
|
* |
701
|
|
|
* @return array An array of sorting configurations for grouping. |
702
|
|
|
*/ |
703
|
1 |
|
public function getGroupSortings() |
704
|
|
|
{ |
705
|
1 |
|
return (array)$this->getQueryParameter('group.sort', []); |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
// faceting |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Adds a query that should be used for grouping. |
712
|
|
|
* |
713
|
|
|
* @param string $query Lucene query for grouping |
714
|
|
|
*/ |
715
|
1 |
|
public function addGroupQuery($query) |
716
|
|
|
{ |
717
|
1 |
|
if (!isset($this->queryParameters['group.query'])) { |
718
|
1 |
|
$this->queryParameters['group.query'] = []; |
719
|
1 |
|
} |
720
|
|
|
|
721
|
1 |
|
$this->queryParameters['group.query'][] = $query; |
722
|
1 |
|
} |
723
|
|
|
|
724
|
|
|
/** |
725
|
|
|
* Gets the queries set for grouping. |
726
|
|
|
* |
727
|
|
|
* @return array An array of queries set for grouping. |
728
|
|
|
*/ |
729
|
1 |
|
public function getGroupQueries() |
730
|
|
|
{ |
731
|
1 |
|
return (array)$this->getQueryParameter('group.query', []); |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
/** |
735
|
|
|
* Sets the maximum number of results to be returned per group. |
736
|
|
|
* |
737
|
|
|
* @param int $numberOfResults Maximum number of results per group to return |
738
|
|
|
*/ |
739
|
1 |
|
public function setNumberOfResultsPerGroup($numberOfResults) |
740
|
|
|
{ |
741
|
1 |
|
$numberOfResults = max(intval($numberOfResults), 0); |
742
|
|
|
|
743
|
1 |
|
$this->queryParameters['group.limit'] = $numberOfResults; |
744
|
1 |
|
} |
745
|
|
|
|
746
|
|
|
// filter |
747
|
|
|
|
748
|
|
|
/** |
749
|
|
|
* Gets the maximum number of results to be returned per group. |
750
|
|
|
* |
751
|
|
|
* @return int Maximum number of results per group to return |
752
|
|
|
*/ |
753
|
1 |
|
public function getNumberOfResultsPerGroup() |
754
|
|
|
{ |
755
|
|
|
// default if nothing else set is 1, @see http://wiki.apache.org/solr/FieldCollapsing |
756
|
1 |
|
$numberOfResultsPerGroup = 1; |
757
|
|
|
|
758
|
1 |
|
if (!empty($this->queryParameters['group.limit'])) { |
759
|
1 |
|
$numberOfResultsPerGroup = $this->queryParameters['group.limit']; |
760
|
1 |
|
} |
761
|
|
|
|
762
|
1 |
|
return $numberOfResultsPerGroup; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
/** |
766
|
|
|
* Activates and deactivates faceting for the current query. |
767
|
|
|
* |
768
|
|
|
* @param bool $faceting TRUE to enable faceting, FALSE to disable faceting |
769
|
|
|
* @return void |
770
|
|
|
*/ |
771
|
31 |
|
public function setFaceting($faceting = true) |
772
|
|
|
{ |
773
|
31 |
|
if ($faceting) { |
774
|
31 |
|
$this->queryParameters['facet'] = 'true'; |
775
|
31 |
|
$this->queryParameters['facet.mincount'] = $this->solrConfiguration->getSearchFacetingMinimumCount(); |
776
|
31 |
|
$this->queryParameters['facet.limit'] = $this->solrConfiguration->getSearchFacetingFacetLimit(); |
777
|
|
|
|
778
|
31 |
|
$this->applyConfiguredFacetSorting(); |
779
|
31 |
|
} else { |
780
|
1 |
|
$this->removeFacetingParametersFromQuery(); |
781
|
|
|
} |
782
|
31 |
|
} |
783
|
|
|
|
784
|
|
|
/** |
785
|
|
|
* Removes all facet.* or f.*.facet.* parameters from the query. |
786
|
|
|
* |
787
|
|
|
* @return void |
788
|
|
|
*/ |
789
|
1 |
|
protected function removeFacetingParametersFromQuery() |
790
|
|
|
{ |
791
|
1 |
|
foreach ($this->queryParameters as $key => $value) { |
792
|
|
|
// remove all facet.* settings |
793
|
1 |
|
if (GeneralUtility::isFirstPartOfStr($key, 'facet')) { |
794
|
1 |
|
unset($this->queryParameters[$key]); |
795
|
1 |
|
} |
796
|
|
|
|
797
|
|
|
// remove all f.*.facet.* settings (overrides for individual fields) |
798
|
1 |
|
if (GeneralUtility::isFirstPartOfStr($key, 'f.') && strpos($key, '.facet.') !== false) { |
799
|
1 |
|
unset($this->queryParameters[$key]); |
800
|
1 |
|
} |
801
|
1 |
|
} |
802
|
1 |
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
* Reads the facet sorting configuration and applies it to the queryParameters. |
806
|
|
|
* |
807
|
|
|
* @return void |
808
|
|
|
*/ |
809
|
31 |
|
protected function applyConfiguredFacetSorting() |
810
|
|
|
{ |
811
|
31 |
|
$sorting = $this->solrConfiguration->getSearchFacetingSortBy(); |
812
|
31 |
|
if (!GeneralUtility::inList('count,index,alpha,lex,1,0,true,false', $sorting)) { |
813
|
|
|
// when the sorting is not in the list of valid values we do not apply it. |
814
|
10 |
|
return; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
// alpha and lex alias for index |
818
|
21 |
|
if ($sorting == 'alpha' || $sorting == 'lex') { |
819
|
1 |
|
$sorting = 'index'; |
820
|
1 |
|
} |
821
|
|
|
|
822
|
21 |
|
$this->queryParameters['facet.sort'] = $sorting; |
823
|
21 |
|
} |
824
|
|
|
|
825
|
|
|
/** |
826
|
|
|
* Sets facet fields for a query. |
827
|
|
|
* |
828
|
|
|
* @param array $facetFields Array of field names |
829
|
|
|
*/ |
830
|
1 |
|
public function setFacetFields(array $facetFields) |
831
|
|
|
{ |
832
|
1 |
|
$this->queryParameters['facet.field'] = []; |
833
|
|
|
|
834
|
1 |
|
foreach ($facetFields as $facetField) { |
835
|
1 |
|
$this->addFacetField($facetField); |
836
|
1 |
|
} |
837
|
1 |
|
} |
838
|
|
|
|
839
|
|
|
/** |
840
|
|
|
* Adds a single facet field. |
841
|
|
|
* |
842
|
|
|
* @param string $facetField field name |
843
|
|
|
*/ |
844
|
2 |
|
public function addFacetField($facetField) |
845
|
|
|
{ |
846
|
2 |
|
$this->queryParameters['facet.field'][] = $facetField; |
847
|
2 |
|
} |
848
|
|
|
|
849
|
|
|
/** |
850
|
|
|
* Removes a filter on a field |
851
|
|
|
* |
852
|
|
|
* @param string $filterFieldName The field name the filter should be removed for |
853
|
|
|
* @return void |
854
|
|
|
*/ |
855
|
1 |
|
public function removeFilter($filterFieldName) |
856
|
|
|
{ |
857
|
1 |
|
foreach ($this->filters as $key => $filterString) { |
858
|
1 |
|
if (GeneralUtility::isFirstPartOfStr($filterString, |
859
|
1 |
|
$filterFieldName . ':') |
860
|
1 |
|
) { |
861
|
1 |
|
unset($this->filters[$key]); |
862
|
1 |
|
} |
863
|
1 |
|
} |
864
|
1 |
|
} |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* Removes a filter based on key of filter array |
868
|
|
|
* |
869
|
|
|
* @param string $key array key |
870
|
|
|
*/ |
871
|
1 |
|
public function removeFilterByKey($key) |
872
|
|
|
{ |
873
|
1 |
|
unset($this->filters[$key]); |
874
|
1 |
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* Removes a filter by the filter value. The value has the following format: |
878
|
|
|
* |
879
|
|
|
* "fieldname:value" |
880
|
|
|
* |
881
|
|
|
* @param string $filterString The filter to remove, in the form of field:value |
882
|
|
|
*/ |
883
|
1 |
|
public function removeFilterByValue($filterString) |
884
|
|
|
{ |
885
|
1 |
|
$key = array_search($filterString, $this->filters); |
886
|
1 |
|
if ($key === false) { |
887
|
|
|
// value not found, nothing to do |
888
|
|
|
return; |
889
|
|
|
} |
890
|
1 |
|
unset($this->filters[$key]); |
891
|
1 |
|
} |
892
|
|
|
|
893
|
|
|
/** |
894
|
|
|
* Gets all currently applied filters. |
895
|
|
|
* |
896
|
|
|
* @return array Array of filters |
897
|
|
|
*/ |
898
|
30 |
|
public function getFilters() |
899
|
|
|
{ |
900
|
30 |
|
return $this->filters; |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
// sorting |
904
|
|
|
|
905
|
|
|
/** |
906
|
|
|
* Sets access restrictions for a frontend user. |
907
|
|
|
* |
908
|
|
|
* @param array $groups Array of groups a user has been assigned to |
909
|
|
|
*/ |
910
|
29 |
|
public function setUserAccessGroups(array $groups) |
911
|
|
|
{ |
912
|
29 |
|
$groups = array_map('intval', $groups); |
913
|
29 |
|
$groups[] = 0; // always grant access to public documents |
914
|
29 |
|
$groups = array_unique($groups); |
915
|
29 |
|
sort($groups, SORT_NUMERIC); |
916
|
|
|
|
917
|
29 |
|
$accessFilter = '{!typo3access}' . implode(',', $groups); |
918
|
|
|
|
919
|
29 |
|
foreach ($this->filters as $key => $filter) { |
920
|
25 |
|
if (GeneralUtility::isFirstPartOfStr($filter, '{!typo3access}')) { |
921
|
1 |
|
unset($this->filters[$key]); |
922
|
1 |
|
} |
923
|
29 |
|
} |
924
|
|
|
|
925
|
29 |
|
$this->addFilter($accessFilter); |
926
|
29 |
|
} |
927
|
|
|
|
928
|
|
|
/** |
929
|
|
|
* Adds a filter parameter. |
930
|
|
|
* |
931
|
|
|
* @param string $filterString The filter to add, in the form of field:value |
932
|
|
|
* @return void |
933
|
|
|
*/ |
934
|
37 |
|
public function addFilter($filterString) |
935
|
|
|
{ |
936
|
|
|
// TODO refactor to split filter field and filter value, @see Drupal |
937
|
37 |
|
if ($this->solrConfiguration->getLoggingQueryFilters()) { |
938
|
|
|
$this->logger->log( |
939
|
|
|
SolrLogManager::INFO, |
940
|
|
|
'Adding filter', |
941
|
|
|
[ |
942
|
|
|
$filterString |
943
|
|
|
] |
944
|
|
|
); |
945
|
|
|
} |
946
|
|
|
|
947
|
37 |
|
$this->filters[] = $filterString; |
948
|
37 |
|
} |
949
|
|
|
|
950
|
|
|
|
951
|
|
|
// query parameters |
952
|
|
|
|
953
|
|
|
/** |
954
|
|
|
* Limits the query to certain sites |
955
|
|
|
* |
956
|
|
|
* @param string $allowedSites Comma-separated list of domains |
957
|
|
|
*/ |
958
|
26 |
|
public function setSiteHashFilter($allowedSites) |
959
|
|
|
{ |
960
|
26 |
|
if (trim($allowedSites) === '*') { |
961
|
1 |
|
return; |
962
|
|
|
} |
963
|
|
|
|
964
|
25 |
|
$allowedSites = GeneralUtility::trimExplode(',', $allowedSites); |
965
|
25 |
|
$filters = []; |
966
|
|
|
|
967
|
25 |
|
foreach ($allowedSites as $site) { |
968
|
25 |
|
$siteHash = $this->siteHashService->getSiteHashForDomain($site); |
969
|
25 |
|
$filters[] = 'siteHash:"' . $siteHash . '"'; |
970
|
25 |
|
} |
971
|
|
|
|
972
|
25 |
|
$this->addFilter(implode(' OR ', $filters)); |
973
|
25 |
|
} |
974
|
|
|
|
975
|
|
|
/** |
976
|
|
|
* Limits the query to certain page tree branches |
977
|
|
|
* |
978
|
|
|
* @param string $pageIds Comma-separated list of page IDs |
979
|
|
|
*/ |
980
|
|
|
public function setRootlineFilter($pageIds) |
981
|
|
|
{ |
982
|
|
|
$pageIds = GeneralUtility::trimExplode(',', $pageIds); |
983
|
|
|
$filters = []; |
984
|
|
|
|
985
|
|
|
/** @var $processor PageUidToHierarchy */ |
986
|
|
|
$processor = GeneralUtility::makeInstance(PageUidToHierarchy::class); |
987
|
|
|
$hierarchies = $processor->process($pageIds); |
988
|
|
|
|
989
|
|
|
foreach ($hierarchies as $hierarchy) { |
990
|
|
|
$lastLevel = array_pop($hierarchy); |
991
|
|
|
$filters[] = 'rootline:"' . $lastLevel . '"'; |
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
$this->addFilter(implode(' OR ', $filters)); |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
/** |
998
|
|
|
* Adds a sort field and the sorting direction for that field |
999
|
|
|
* |
1000
|
|
|
* @param string $fieldName The field name to sort by |
1001
|
|
|
* @param string $direction Either ApacheSolrForTypo3\Solr\Query::SORT_ASC to sort the field ascending or ApacheSolrForTypo3\Solr\Query::SORT_DESC to sort descending |
1002
|
|
|
* @return void |
1003
|
|
|
* @throws \InvalidArgumentException if the $direction parameter given is neither ApacheSolrForTypo3\Solr\Query::SORT_ASC nor ApacheSolrForTypo3\Solr\Query::SORT_DESC |
1004
|
|
|
* @deprecated since 6.1 will be removed in 7.0 |
1005
|
|
|
*/ |
1006
|
1 |
|
public function addSortField($fieldName, $direction) |
1007
|
|
|
{ |
1008
|
1 |
|
GeneralUtility::logDeprecatedFunction(); |
1009
|
|
|
|
1010
|
1 |
|
$isValidSorting = $direction === self::SORT_DESC || $direction === self::SORT_ASC; |
1011
|
1 |
|
if (!$isValidSorting) { |
1012
|
1 |
|
throw new \InvalidArgumentException('Invalid sort direction "' . $direction . '"', 1235051723); |
1013
|
|
|
} |
1014
|
|
|
|
1015
|
1 |
|
$this->sortingFields[$fieldName] = $direction; |
|
|
|
|
1016
|
1 |
|
} |
1017
|
|
|
|
1018
|
|
|
/** |
1019
|
|
|
* Gets the currently set sorting fields and their sorting directions |
1020
|
|
|
* |
1021
|
|
|
* @return array An associative array with the field names as key and their sorting direction as value |
1022
|
|
|
* @deprecated since 6.1 will be removed in 7.0 |
1023
|
|
|
*/ |
1024
|
1 |
|
public function getSortingFields() |
1025
|
|
|
{ |
1026
|
1 |
|
GeneralUtility::logDeprecatedFunction(); |
1027
|
|
|
|
1028
|
1 |
|
return $this->sortingFields; |
|
|
|
|
1029
|
|
|
} |
1030
|
|
|
|
1031
|
|
|
/** |
1032
|
|
|
* Gets the list of fields a query will return. |
1033
|
|
|
* |
1034
|
|
|
* @return array Array of field names the query will return |
1035
|
|
|
*/ |
1036
|
6 |
|
public function getFieldList() |
1037
|
|
|
{ |
1038
|
6 |
|
return $this->fieldList; |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
/** |
1042
|
|
|
* Sets the fields to return by a query. |
1043
|
|
|
* |
1044
|
|
|
* @param array|string $fieldList an array or comma-separated list of field names |
1045
|
|
|
* @throws \UnexpectedValueException on parameters other than comma-separated lists and arrays |
1046
|
|
|
*/ |
1047
|
3 |
|
public function setFieldList($fieldList = ['*', 'score']) |
1048
|
|
|
{ |
1049
|
3 |
|
if (is_string($fieldList)) { |
1050
|
2 |
|
$fieldList = GeneralUtility::trimExplode(',', $fieldList); |
1051
|
2 |
|
} |
1052
|
|
|
|
1053
|
3 |
|
if (!is_array($fieldList) || empty($fieldList)) { |
1054
|
1 |
|
throw new \UnexpectedValueException( |
1055
|
1 |
|
'Field list must be a comma-separated list or array and must not be empty.', |
1056
|
|
|
1310740308 |
1057
|
1 |
|
); |
1058
|
|
|
} |
1059
|
|
|
|
1060
|
3 |
|
$this->fieldList = $fieldList; |
1061
|
3 |
|
} |
1062
|
|
|
|
1063
|
|
|
/** |
1064
|
|
|
* Gets the query type, Solr's qt parameter. |
1065
|
|
|
* |
1066
|
|
|
* @return string Query type, qt parameter. |
1067
|
|
|
*/ |
1068
|
1 |
|
public function getQueryType() |
1069
|
|
|
{ |
1070
|
1 |
|
return $this->queryParameters['qt']; |
1071
|
|
|
} |
1072
|
|
|
|
1073
|
|
|
/** |
1074
|
|
|
* Sets the query type, Solr's qt parameter. |
1075
|
|
|
* |
1076
|
|
|
* @param string|bool $queryType String query type or boolean FALSE to disable / reset the qt parameter. |
1077
|
|
|
* @see http://wiki.apache.org/solr/CoreQueryParameters#qt |
1078
|
|
|
*/ |
1079
|
2 |
|
public function setQueryType($queryType) |
1080
|
|
|
{ |
1081
|
2 |
|
if ($queryType) { |
1082
|
2 |
|
$this->queryParameters['qt'] = $queryType; |
1083
|
2 |
|
} else { |
1084
|
1 |
|
unset($this->queryParameters['qt']); |
1085
|
|
|
} |
1086
|
2 |
|
} |
1087
|
|
|
|
1088
|
|
|
/** |
1089
|
|
|
* Sets the query operator to AND or OR. Unsets the query operator (actually |
1090
|
|
|
* sets it back to default) for FALSE. |
1091
|
|
|
* |
1092
|
|
|
* @param string|bool $operator AND or OR, FALSE to unset |
1093
|
|
|
*/ |
1094
|
1 |
|
public function setOperator($operator) |
1095
|
|
|
{ |
1096
|
1 |
|
if (in_array($operator, [self::OPERATOR_AND, self::OPERATOR_OR])) { |
1097
|
1 |
|
$this->queryParameters['q.op'] = $operator; |
1098
|
1 |
|
} |
1099
|
|
|
|
1100
|
1 |
|
if ($operator === false) { |
1101
|
1 |
|
unset($this->queryParameters['q.op']); |
1102
|
1 |
|
} |
1103
|
1 |
|
} |
1104
|
|
|
|
1105
|
|
|
/** |
1106
|
|
|
* Gets the alternative query, Solr's q.alt parameter. |
1107
|
|
|
* |
1108
|
|
|
* @return string Alternative query, q.alt parameter. |
1109
|
|
|
*/ |
1110
|
1 |
|
public function getAlternativeQuery() |
1111
|
|
|
{ |
1112
|
1 |
|
return $this->queryParameters['q.alt']; |
1113
|
|
|
} |
1114
|
|
|
|
1115
|
|
|
/** |
1116
|
|
|
* Sets an alternative query, Solr's q.alt parameter. |
1117
|
|
|
* |
1118
|
|
|
* This query supports the complete Lucene Query Language. |
1119
|
|
|
* |
1120
|
|
|
* @param mixed $alternativeQuery String alternative query or boolean FALSE to disable / reset the q.alt parameter. |
1121
|
|
|
* @see http://wiki.apache.org/solr/DisMaxQParserPlugin#q.alt |
1122
|
|
|
*/ |
1123
|
26 |
|
public function setAlternativeQuery($alternativeQuery) |
1124
|
|
|
{ |
1125
|
26 |
|
if ($alternativeQuery) { |
1126
|
26 |
|
$this->queryParameters['q.alt'] = $alternativeQuery; |
1127
|
26 |
|
} else { |
1128
|
1 |
|
unset($this->queryParameters['q.alt']); |
1129
|
|
|
} |
1130
|
26 |
|
} |
1131
|
|
|
|
1132
|
|
|
// keywords |
1133
|
|
|
|
1134
|
|
|
/** |
1135
|
|
|
* Set the query to omit the response header |
1136
|
|
|
* |
1137
|
|
|
* @param bool $omitHeader TRUE (default) to omit response headers, FALSE to re-enable |
1138
|
|
|
*/ |
1139
|
1 |
|
public function setOmitHeader($omitHeader = true) |
1140
|
|
|
{ |
1141
|
1 |
|
if ($omitHeader) { |
1142
|
1 |
|
$this->queryParameters['omitHeader'] = 'true'; |
1143
|
1 |
|
} else { |
1144
|
1 |
|
unset($this->queryParameters['omitHeader']); |
1145
|
|
|
} |
1146
|
1 |
|
} |
1147
|
|
|
|
1148
|
|
|
/** |
1149
|
|
|
* Get the query keywords, keywords are escaped. |
1150
|
|
|
* |
1151
|
|
|
* @return string query keywords |
1152
|
|
|
*/ |
1153
|
20 |
|
public function getKeywords() |
1154
|
|
|
{ |
1155
|
20 |
|
return $this->keywords; |
1156
|
|
|
} |
1157
|
|
|
|
1158
|
|
|
/** |
1159
|
|
|
* Sets the query keywords, escapes them as needed for Solr/Lucene. |
1160
|
|
|
* |
1161
|
|
|
* @param string $keywords user search terms/keywords |
1162
|
|
|
*/ |
1163
|
132 |
|
public function setKeywords($keywords) |
1164
|
|
|
{ |
1165
|
132 |
|
$this->keywords = $this->escape($keywords); |
1166
|
132 |
|
$this->keywordsRaw = $keywords; |
1167
|
132 |
|
} |
1168
|
|
|
|
1169
|
|
|
/** |
1170
|
|
|
* Gets the cleaned keywords so that it can be used in templates f.e. |
1171
|
|
|
* |
1172
|
|
|
* @return string The cleaned keywords. |
1173
|
|
|
*/ |
1174
|
18 |
|
public function getKeywordsCleaned() |
1175
|
|
|
{ |
1176
|
18 |
|
return $this->cleanKeywords($this->keywordsRaw); |
1177
|
|
|
} |
1178
|
|
|
|
1179
|
|
|
/** |
1180
|
|
|
* Helper method to escape/encode keywords for use in HTML |
1181
|
|
|
* |
1182
|
|
|
* @param string $keywords Keywords to prepare for use in HTML |
1183
|
|
|
* @return string Encoded keywords |
1184
|
|
|
*/ |
1185
|
22 |
|
public static function cleanKeywords($keywords) |
1186
|
|
|
{ |
1187
|
22 |
|
$keywords = trim($keywords); |
1188
|
22 |
|
$keywords = GeneralUtility::removeXSS($keywords); |
1189
|
22 |
|
$keywords = htmlentities($keywords, ENT_QUOTES, |
1190
|
22 |
|
$GLOBALS['TSFE']->metaCharset); |
1191
|
|
|
|
1192
|
|
|
// escape triple hashes as they are used in the template engine |
1193
|
|
|
// TODO remove after switching to fluid templates |
1194
|
22 |
|
$keywords = Template::escapeMarkers($keywords); |
1195
|
|
|
|
1196
|
22 |
|
return $keywords; |
1197
|
|
|
} |
1198
|
|
|
|
1199
|
|
|
// relevance, matching |
1200
|
|
|
|
1201
|
|
|
/** |
1202
|
|
|
* Gets the raw, unescaped, unencoded keywords. |
1203
|
|
|
* |
1204
|
|
|
* USE WITH CAUTION! |
1205
|
|
|
* |
1206
|
|
|
* @return string raw keywords |
1207
|
|
|
*/ |
1208
|
19 |
|
public function getKeywordsRaw() |
1209
|
|
|
{ |
1210
|
19 |
|
return $this->keywordsRaw; |
1211
|
|
|
} |
1212
|
|
|
|
1213
|
|
|
/** |
1214
|
|
|
* Sets the minimum match (mm) parameter |
1215
|
|
|
* |
1216
|
|
|
* @param mixed $minimumMatch Minimum match parameter as string or boolean FALSE to disable / reset the mm parameter |
1217
|
|
|
* @see http://wiki.apache.org/solr/DisMaxRequestHandler#mm_.28Minimum_.27Should.27_Match.29 |
1218
|
|
|
*/ |
1219
|
1 |
|
public function setMinimumMatch($minimumMatch) |
1220
|
|
|
{ |
1221
|
1 |
|
if (is_string($minimumMatch) && !empty($minimumMatch)) { |
1222
|
1 |
|
$this->queryParameters['mm'] = $minimumMatch; |
1223
|
1 |
|
} else { |
1224
|
1 |
|
unset($this->queryParameters['mm']); |
1225
|
|
|
} |
1226
|
1 |
|
} |
1227
|
|
|
|
1228
|
|
|
/** |
1229
|
|
|
* Sets the boost function (bf) parameter |
1230
|
|
|
* |
1231
|
|
|
* @param mixed $boostFunction boost function parameter as string or boolean FALSE to disable / reset the bf parameter |
1232
|
|
|
* @see http://wiki.apache.org/solr/DisMaxRequestHandler#bf_.28Boost_Functions.29 |
1233
|
|
|
*/ |
1234
|
1 |
|
public function setBoostFunction($boostFunction) |
1235
|
|
|
{ |
1236
|
1 |
|
if (is_string($boostFunction) && !empty($boostFunction)) { |
1237
|
1 |
|
$this->queryParameters['bf'] = $boostFunction; |
1238
|
1 |
|
} else { |
1239
|
1 |
|
unset($this->queryParameters['bf']); |
1240
|
|
|
} |
1241
|
1 |
|
} |
1242
|
|
|
|
1243
|
|
|
// query fields |
1244
|
|
|
// TODO move up to field list methods |
1245
|
|
|
|
1246
|
|
|
/** |
1247
|
|
|
* Sets the boost query (bq) parameter |
1248
|
|
|
* |
1249
|
|
|
* @param mixed $boostQuery boost query parameter as string or array to set a boost query or boolean FALSE to disable / reset the bq parameter |
1250
|
|
|
* @see http://wiki.apache.org/solr/DisMaxQParserPlugin#bq_.28Boost_Query.29 |
1251
|
|
|
*/ |
1252
|
1 |
|
public function setBoostQuery($boostQuery) |
1253
|
|
|
{ |
1254
|
1 |
|
if ((is_string($boostQuery) || is_array($boostQuery)) && !empty($boostQuery)) { |
1255
|
1 |
|
$this->queryParameters['bq'] = $boostQuery; |
1256
|
1 |
|
} else { |
1257
|
1 |
|
unset($this->queryParameters['bq']); |
1258
|
|
|
} |
1259
|
1 |
|
} |
1260
|
|
|
|
1261
|
|
|
/** |
1262
|
|
|
* Gets a specific query parameter by its name. |
1263
|
|
|
* |
1264
|
|
|
* @param string $parameterName The parameter to return |
1265
|
|
|
* @param mixed $defaultIfEmpty |
1266
|
|
|
* @return mixed The parameter's value or $defaultIfEmpty if not set |
1267
|
|
|
*/ |
1268
|
10 |
|
public function getQueryParameter($parameterName, $defaultIfEmpty = null) |
1269
|
|
|
{ |
1270
|
10 |
|
$parameters = $this->getQueryParameters(); |
1271
|
10 |
|
return isset($parameters[$parameterName]) ? $parameters[$parameterName] : $defaultIfEmpty; |
1272
|
|
|
} |
1273
|
|
|
|
1274
|
|
|
/** |
1275
|
|
|
* Builds an array of query parameters to use for the search query. |
1276
|
|
|
* |
1277
|
|
|
* @return array An array ready to use with query parameters |
1278
|
|
|
*/ |
1279
|
64 |
|
public function getQueryParameters() |
1280
|
|
|
{ |
1281
|
64 |
|
$queryParameters = array_merge( |
1282
|
|
|
[ |
1283
|
64 |
|
'fl' => implode(',', $this->fieldList), |
1284
|
64 |
|
'fq' => array_values($this->filters) |
1285
|
64 |
|
], |
1286
|
64 |
|
$this->queryParameters |
1287
|
64 |
|
); |
1288
|
|
|
|
1289
|
64 |
|
$queryFieldString = $this->getQueryFieldsAsString(); |
1290
|
64 |
|
if (!empty($queryFieldString)) { |
1291
|
27 |
|
$queryParameters['qf'] = $queryFieldString; |
1292
|
27 |
|
} |
1293
|
|
|
|
1294
|
64 |
|
return $queryParameters; |
1295
|
|
|
} |
1296
|
|
|
|
1297
|
|
|
// general query parameters |
1298
|
|
|
|
1299
|
|
|
/** |
1300
|
|
|
* Compiles the query fields into a string to be used in Solr's qf parameter. |
1301
|
|
|
* |
1302
|
|
|
* @return string A string of query fields with their associated boosts |
1303
|
|
|
*/ |
1304
|
66 |
|
public function getQueryFieldsAsString() |
1305
|
|
|
{ |
1306
|
66 |
|
$queryFieldString = ''; |
1307
|
|
|
|
1308
|
66 |
|
foreach ($this->queryFields as $fieldName => $fieldBoost) { |
1309
|
28 |
|
$queryFieldString .= $fieldName; |
1310
|
|
|
|
1311
|
28 |
|
if ($fieldBoost != 1.0) { |
1312
|
28 |
|
$queryFieldString .= '^' . number_format($fieldBoost, 1, '.', ''); |
1313
|
28 |
|
} |
1314
|
|
|
|
1315
|
28 |
|
$queryFieldString .= ' '; |
1316
|
66 |
|
} |
1317
|
|
|
|
1318
|
66 |
|
return trim($queryFieldString); |
1319
|
|
|
} |
1320
|
|
|
|
1321
|
|
|
/** |
1322
|
|
|
* Enables or disables highlighting of search terms in result teasers. |
1323
|
|
|
* |
1324
|
|
|
* @param bool $highlighting Enables highlighting when set to TRUE, deactivates highlighting when set to FALSE, defaults to TRUE. |
1325
|
|
|
* @param int $fragmentSize Size, in characters, of fragments to consider for highlighting. |
1326
|
|
|
* @see http://wiki.apache.org/solr/HighlightingParameters |
1327
|
|
|
* @return void |
1328
|
|
|
*/ |
1329
|
31 |
|
public function setHighlighting($highlighting = true, $fragmentSize = 200) |
1330
|
|
|
{ |
1331
|
31 |
|
if ($highlighting) { |
1332
|
31 |
|
$this->queryParameters['hl'] = 'true'; |
1333
|
31 |
|
$this->queryParameters['hl.fragsize'] = (int)$fragmentSize; |
1334
|
|
|
|
1335
|
31 |
|
$highlightingFields = $this->solrConfiguration->getSearchResultsHighlightingFields(); |
1336
|
31 |
|
if ($highlightingFields != '') { |
1337
|
25 |
|
$this->queryParameters['hl.fl'] = $highlightingFields; |
1338
|
25 |
|
} |
1339
|
|
|
|
1340
|
|
|
// the fast vector highlighter can only be used, when the fragmentSize is |
1341
|
|
|
// higher then 17 otherwise solr throws an exception |
1342
|
31 |
|
$useFastVectorHighlighter = ($fragmentSize >= 18); |
1343
|
31 |
|
$wrap = explode('|', $this->solrConfiguration->getSearchResultsHighlightingWrap()); |
1344
|
|
|
|
1345
|
31 |
|
if ($useFastVectorHighlighter) { |
1346
|
29 |
|
$this->queryParameters['hl.useFastVectorHighlighter'] = 'true'; |
1347
|
29 |
|
$this->queryParameters['hl.tag.pre'] = $wrap[0]; |
1348
|
29 |
|
$this->queryParameters['hl.tag.post'] = $wrap[1]; |
1349
|
29 |
|
} |
1350
|
|
|
|
1351
|
31 |
|
if (isset($wrap[0]) && isset($wrap[1])) { |
1352
|
26 |
|
$this->queryParameters['hl.simple.pre'] = $wrap[0]; |
1353
|
26 |
|
$this->queryParameters['hl.simple.post'] = $wrap[1]; |
1354
|
26 |
|
} |
1355
|
31 |
|
} else { |
1356
|
|
|
// remove all hl.* settings |
1357
|
1 |
|
foreach ($this->queryParameters as $key => $value) { |
1358
|
1 |
|
if (GeneralUtility::isFirstPartOfStr($key, 'hl')) { |
1359
|
1 |
|
unset($this->queryParameters[$key]); |
1360
|
1 |
|
} |
1361
|
1 |
|
} |
1362
|
|
|
} |
1363
|
31 |
|
} |
1364
|
|
|
|
1365
|
|
|
// misc |
1366
|
|
|
|
1367
|
|
|
/** |
1368
|
|
|
* Enables or disables spellchecking for the query. |
1369
|
|
|
* |
1370
|
|
|
* @param bool $spellchecking Enables spellchecking when set to TRUE, deactivates spellchecking when set to FALSE, defaults to TRUE. |
1371
|
|
|
*/ |
1372
|
22 |
|
public function setSpellchecking($spellchecking = true) |
1373
|
|
|
{ |
1374
|
22 |
|
if ($spellchecking) { |
1375
|
22 |
|
$this->queryParameters['spellcheck'] = 'true'; |
1376
|
22 |
|
$this->queryParameters['spellcheck.collate'] = 'true'; |
1377
|
22 |
|
$maxCollationTries = $this->solrConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(); |
1378
|
22 |
|
$this->addQueryParameter('spellcheck.maxCollationTries', $maxCollationTries); |
1379
|
22 |
|
} else { |
1380
|
1 |
|
unset($this->queryParameters['spellcheck']); |
1381
|
1 |
|
unset($this->queryParameters['spellcheck.collate']); |
1382
|
1 |
|
unset($this->queryParameters['spellcheck.maxCollationTries']); |
1383
|
|
|
} |
1384
|
22 |
|
} |
1385
|
|
|
|
1386
|
|
|
/** |
1387
|
|
|
* Adds any generic query parameter. |
1388
|
|
|
* |
1389
|
|
|
* @param string $parameterName Query parameter name |
1390
|
|
|
* @param string $parameterValue Parameter value |
1391
|
|
|
*/ |
1392
|
30 |
|
public function addQueryParameter($parameterName, $parameterValue) |
1393
|
|
|
{ |
1394
|
30 |
|
$this->queryParameters[$parameterName] = $parameterValue; |
1395
|
30 |
|
} |
1396
|
|
|
|
1397
|
|
|
/** |
1398
|
|
|
* Sets the sort parameter. |
1399
|
|
|
* |
1400
|
|
|
* $sorting must include a field name (or the pseudo-field score), |
1401
|
|
|
* followed by a space, |
1402
|
|
|
* followed by a sort direction (asc or desc). |
1403
|
|
|
* |
1404
|
|
|
* Multiple fallback sortings can be separated by comma, |
1405
|
|
|
* ie: <field name> <direction>[,<field name> <direction>]... |
1406
|
|
|
* |
1407
|
|
|
* @param string|bool $sorting Either a comma-separated list of sort fields and directions or FALSE to reset sorting to the default behavior (sort by score / relevance) |
1408
|
|
|
* @see http://wiki.apache.org/solr/CommonQueryParameters#sort |
1409
|
|
|
*/ |
1410
|
3 |
|
public function setSorting($sorting) |
1411
|
|
|
{ |
1412
|
3 |
|
if ($sorting) { |
1413
|
3 |
|
if (!is_string($sorting)) { |
1414
|
|
|
throw new \InvalidArgumentException('Sorting needs to be a string!'); |
1415
|
|
|
} |
1416
|
3 |
|
$sortParameter = $this->removeRelevanceSortField($sorting); |
1417
|
3 |
|
$this->queryParameters['sort'] = $sortParameter; |
1418
|
3 |
|
} else { |
1419
|
1 |
|
unset($this->queryParameters['sort']); |
1420
|
|
|
} |
1421
|
3 |
|
} |
1422
|
|
|
|
1423
|
|
|
/** |
1424
|
|
|
* Removes the relevance sort field if present in the sorting field definition. |
1425
|
|
|
* |
1426
|
|
|
* @param string $sorting |
1427
|
|
|
* @return string |
1428
|
|
|
*/ |
1429
|
3 |
|
protected function removeRelevanceSortField($sorting) |
1430
|
|
|
{ |
1431
|
3 |
|
$sortParameter = $sorting; |
1432
|
3 |
|
list($sortField) = explode(' ', $sorting); |
1433
|
3 |
|
if ($sortField == 'relevance') { |
1434
|
1 |
|
$sortParameter = ''; |
1435
|
1 |
|
return $sortParameter; |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
3 |
|
return $sortParameter; |
1439
|
|
|
} |
1440
|
|
|
|
1441
|
|
|
/** |
1442
|
|
|
* Enables or disables the debug parameter for the query. |
1443
|
|
|
* |
1444
|
|
|
* @param bool $debugMode Enables debugging when set to TRUE, deactivates debugging when set to FALSE, defaults to TRUE. |
1445
|
|
|
*/ |
1446
|
|
|
public function setDebugMode($debugMode = true) |
1447
|
|
|
{ |
1448
|
|
|
if ($debugMode) { |
1449
|
|
|
$this->queryParameters['debugQuery'] = 'true'; |
1450
|
|
|
$this->queryParameters['echoParams'] = 'all'; |
1451
|
|
|
} else { |
1452
|
|
|
unset($this->queryParameters['debugQuery']); |
1453
|
|
|
unset($this->queryParameters['echoParams']); |
1454
|
|
|
} |
1455
|
|
|
} |
1456
|
|
|
|
1457
|
|
|
/** |
1458
|
|
|
* Returns the link target page id. |
1459
|
|
|
* |
1460
|
|
|
* @return int |
1461
|
|
|
*/ |
1462
|
2 |
|
public function getLinkTargetPageId() |
1463
|
|
|
{ |
1464
|
2 |
|
return $this->linkTargetPageId; |
1465
|
|
|
} |
1466
|
|
|
|
1467
|
|
|
/** |
1468
|
|
|
* Activates the collapsing on the configured field, if collapsing was enabled. |
1469
|
|
|
* |
1470
|
|
|
* @return bool |
1471
|
|
|
*/ |
1472
|
131 |
|
protected function initializeCollapsingFromConfiguration() |
1473
|
|
|
{ |
1474
|
|
|
// check collapsing |
1475
|
131 |
|
if ($this->solrConfiguration->getSearchVariants()) { |
1476
|
3 |
|
$collapseField = $this->solrConfiguration->getSearchVariantsField(); |
1477
|
3 |
|
$this->setVariantField($collapseField); |
1478
|
3 |
|
$this->setCollapsing(true); |
1479
|
|
|
|
1480
|
3 |
|
return true; |
1481
|
|
|
} |
1482
|
|
|
|
1483
|
128 |
|
return false; |
1484
|
|
|
} |
1485
|
|
|
} |
1486
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.