Completed
Push — master ( ee9c7d...9819c1 )
by Rafael
14:45
created

Search::applyHtmlSpecialCharsOnSingleFieldValue()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 2
nop 1
crap 3
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\Search\ResultSet\Result\Parser\DocumentEscapeService;
28
use ApacheSolrForTypo3\Solr\Search\FacetsModifier;
29
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
30
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
31
use ApacheSolrForTypo3\Solr\System\Solr\SolrCommunicationException;
32
use TYPO3\CMS\Core\SingletonInterface;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
35
/**
36
 * Class to handle solr search requests
37
 *
38
 * @author Ingo Renner <[email protected]>
39
 */
40
class Search
41
{
42
43
    /**
44
     * An instance of the Solr service
45
     *
46
     * @var SolrService
47
     */
48
    protected $solr = null;
49
50
    /**
51
     * The search query
52
     *
53
     * @var Query
54
     */
55
    protected $query = null;
56
57
    /**
58
     * The search response
59
     *
60
     * @var \Apache_Solr_Response
61
     */
62
    protected $response = null;
63
64
    /**
65
     * Flag for marking a search
66
     *
67
     * @var bool
68
     */
69
    protected $hasSearched = false;
70
71
    /**
72
     * @var TypoScriptConfiguration
73
     */
74
    protected $configuration;
75
76
    // TODO Override __clone to reset $response and $hasSearched
77
78
    /**
79
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
80
     */
81
    protected $logger = null;
82
83
    /**
84
     * Constructor
85
     *
86
     * @param SolrService $solrConnection The Solr connection to use for searching
87 43
     */
88
    public function __construct(SolrService $solrConnection = null)
89 43
    {
90
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
91 43
92
        $this->solr = $solrConnection;
93 43
94
        if (is_null($solrConnection)) {
95 1
            /** @var $connectionManager ConnectionManager */
96 1
            $connectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
97
            $this->solr = $connectionManager->getConnectionByPageId($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sys_language_uid);
98
        }
99 43
100 43
        $this->configuration = Util::getSolrConfiguration();
101
    }
102
103
    /**
104
     * Gets the Solr connection used by this search.
105
     *
106
     * @return SolrService Solr connection
107
     */
108
    public function getSolrConnection()
109
    {
110
        return $this->solr;
111
    }
112
113
    /**
114
     * Sets the Solr connection used by this search.
115
     *
116
     * Since ApacheSolrForTypo3\Solr\Search is a \TYPO3\CMS\Core\SingletonInterface, this is needed to
117
     * be able to switch between multiple cores/connections during
118
     * one request
119
     *
120
     * @param SolrService $solrConnection
121
     */
122
    public function setSolrConnection(SolrService $solrConnection)
123
    {
124
        $this->solr = $solrConnection;
125
    }
126
127
    /**
128
     * Executes a query against a Solr server.
129
     *
130
     * 1) Gets the query string
131
     * 2) Conducts the actual search
132
     * 3) Checks debug settings
133
     *
134
     * @param Query $query The query with keywords, filters, and so on.
135
     * @param int $offset Result offset for pagination.
136
     * @param int $limit Maximum number of results to return. If set to NULL, this value is taken from the query object.
137
     * @return \Apache_Solr_Response Solr response
138 37
     */
139
    public function search(Query $query, $offset = 0, $limit = 10)
0 ignored issues
show
Best Practice introduced by
Using PHP4-style constructors that are named like the class is not recommend; better use the more explicit __construct method.
Loading history...
140 37
    {
141
        $this->query = $query;
142 37
143 32
        if (empty($limit)) {
144
            $limit = $query->getResultsPerPage();
145
        }
146
147 37
        try {
148 37
            $response = $this->solr->search(
149 37
                $query->getQueryString(),
150 37
                $offset,
151 37
                $limit,
152
                $query->getQueryParameters()
153
            );
154 34
155
            if ($this->configuration->getLoggingQueryQueryString()) {
156
                $this->logger->log(
157
                    SolrLogManager::INFO,
158
                    'Querying Solr, getting result',
159
                    [
160
                        'query string' => $query->getQueryString(),
161
                        'query parameters' => $query->getQueryParameters(),
162 34
                        'response' => json_decode($response->getRawResponse(),
163
                            true)
164
                    ]
165
                );
166 3
            }
167 3
        } catch (SolrCommunicationException $e) {
168 3
            if ($this->configuration->getLoggingExceptions()) {
169 3
                $this->logger->log(
170 3
                    SolrLogManager::ERROR,
171
                    'Exception while querying Solr',
172 3
                    [
173 3
                        'exception' => $e->__toString(),
174 3
                        'query' => (array)$query,
175 3
                        'offset' => $offset,
176
                        'limit' => $limit
177
                    ]
178
                );
179
            }
180 3
181
            throw $e;
182
        }
183 34
184 34
        $this->response = $response;
185
        $this->hasSearched = true;
186 34
187
        return $this->response;
188
    }
189
190
    /**
191
     * Sends a ping to the solr server to see whether it is available.
192
     *
193
     * @param bool $useCache Set to true if the cache should be used.
194
     * @return bool Returns TRUE on successful ping.
195
     * @throws \Exception Throws an exception in case ping was not successful.
196
     */
197
    public function ping($useCache = true)
198
    {
199
        $solrAvailable = false;
200
201
        try {
202
            if (!$this->solr->ping(2, $useCache)) {
203
                throw new \Exception('Solr Server not responding.', 1237475791);
204
            }
205
206
            $solrAvailable = true;
207
        } catch (\Exception $e) {
208
            if ($this->configuration->getLoggingExceptions()) {
209
                $this->logger->log(
210
                    SolrLogManager::ERROR,
211
                    'Exception while trying to ping the solr server',
212
                    [
213
                        $e->__toString()
214
                    ]
215
                );
216
            }
217
        }
218
219
        return $solrAvailable;
220
    }
221
222
    /**
223
     * checks whether a search has been executed
224
     *
225
     * @return bool    TRUE if there was a search, FALSE otherwise (if the user just visited the search page f.e.)
226 30
     */
227
    public function hasSearched()
228 30
    {
229
        return $this->hasSearched;
230
    }
231
232
    /**
233
     * Gets the query object.
234
     *
235
     * @return Query Query
236 27
     */
237
    public function getQuery()
238 27
    {
239
        return $this->query;
240
    }
241
242
    /**
243
     * Gets the Solr response
244
     *
245
     * @return \Apache_Solr_Response
246 28
     */
247
    public function getResponse()
248 28
    {
249
        return $this->response;
250
    }
251
252
    public function getRawResponse()
253
    {
254
        return $this->response->getRawResponse();
255
    }
256 22
257
    public function getResponseHeader()
258 22
    {
259
        return $this->getResponse()->responseHeader;
260
    }
261 28
262
    public function getResponseBody()
263 28
    {
264
        return $this->getResponse()->response;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
265
    }
266
267
    /**
268
     * Returns all results documents raw. Use with caution!
269
     *
270
     * @deprecated Since 8.0.0 will be removed in 9.0.0. Use $resultSet->getSearchResults() this will be initialized by the parser depending on the settings
271 27
     * @return \Apache_Solr_Document[]
272
     */
273 27
    public function getResultDocumentsRaw()
274
    {
275
        GeneralUtility::logDeprecatedFunction();
276
        return $this->getResponseBody()->docs;
277
    }
278
279
    /**
280
     * Returns all result documents but applies htmlspecialchars() on all fields retrieved
281
     * from solr except the configured fields in plugin.tx_solr.search.trustedFields
282 3
     *
283
     * @deprecated Since 8.0.0 will be removed in 9.0.0. Use DocumentEscapeService or
284 3
     * $resultSet->getSearchResults() this will be initialized by the parser depending on the settings.
285
     * @return \Apache_Solr_Document[]
286
     */
287
    public function getResultDocumentsEscaped()
288
    {
289
        GeneralUtility::logDeprecatedFunction();
290
        /** @var $escapeService DocumentEscapeService */
291
        $escapeService = GeneralUtility::makeInstance(DocumentEscapeService::class, $this->configuration);
292
        return $escapeService->applyHtmlSpecialCharsOnAllFields($this->getResponseBody()->docs);
293
    }
294 3
295
    /**
296 3
     * Gets the time Solr took to execute the query and return the result.
297
     *
298 3
     * @return int Query time in milliseconds
299 3
     */
300
    public function getQueryTime()
301 3
    {
302 3
        return $this->getResponseHeader()->QTime;
303
    }
304 3
305
    /**
306
     * Gets the number of results per page.
307 3
     *
308
     * @return int Number of results per page
309
     */
310 3
    public function getResultsPerPage()
311
    {
312
        return $this->getResponseHeader()->params->rows;
313 3
    }
314
315
    /**
316
     * Gets all facets with their fields, options, and counts.
317
     *
318
     * @return array
319
     */
320
    public function getFacetCounts()
321
    {
322 3
        static $facetCountsModified = false;
323
        static $facetCounts = null;
324 3
325 3
        $unmodifiedFacetCounts = $this->response->facet_counts;
0 ignored issues
show
Bug introduced by
The property facet_counts does not seem to exist in Apache_Solr_Response.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
326 3
327
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyFacets'])) {
328
            if (!$facetCountsModified) {
329 3
                $facetCounts = $unmodifiedFacetCounts;
330
331
                foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyFacets'] as $classReference) {
332 3
                    $facetsModifier = GeneralUtility::getUserObj($classReference);
333
334
                    if ($facetsModifier instanceof FacetsModifier) {
335
                        $facetCounts = $facetsModifier->modifyFacets($facetCounts);
336
                        $facetCountsModified = true;
337
                    } else {
338
                        throw new \UnexpectedValueException(
339
                            get_class($facetsModifier) . ' must implement interface ' . FacetsModifier::class,
340 22
                            1310387526
341
                        );
342 22
                    }
343
                }
344
            }
345
        } else {
346
            $facetCounts = $unmodifiedFacetCounts;
347
        }
348
349
        return $facetCounts;
350
    }
351
352
    public function getFacetFieldOptions($facetField)
353
    {
354
        $facetOptions = null;
355
356
        if (property_exists($this->getFacetCounts()->facet_fields,
357
            $facetField)) {
358
            $facetOptions = get_object_vars($this->getFacetCounts()->facet_fields->$facetField);
359
        }
360
361
        return $facetOptions;
362
    }
363
364
    public function getFacetQueryOptions($facetField)
365
    {
366
        $options = [];
367
368
        $facetQueries = get_object_vars($this->getFacetCounts()->facet_queries);
369
        foreach ($facetQueries as $facetQuery => $numberOfResults) {
370
            // remove tags from the facet.query response, for facet.field
371
            // and facet.range Solr does that on its own automatically
372
            $facetQuery = preg_replace('/^\{!ex=[^\}]*\}(.*)/', '\\1',
373
                $facetQuery);
374
375
            if (GeneralUtility::isFirstPartOfStr($facetQuery, $facetField)) {
376
                $options[$facetQuery] = $numberOfResults;
377
            }
378
        }
379
380
        // filter out queries with no results
381
        $options = array_filter($options);
382
383
        return $options;
384
    }
385
386
    public function getFacetRangeOptions($rangeFacetField)
387
    {
388
        return get_object_vars($this->getFacetCounts()->facet_ranges->$rangeFacetField);
389
    }
390
391
    public function getNumberOfResults()
392
    {
393
        return $this->response->response->numFound;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
394
    }
395
396
    /**
397
     * Gets the result offset.
398
     *
399
     * @return int Result offset
400
     */
401
    public function getResultOffset()
402
    {
403
        return $this->response->response->start;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
404
    }
405
406
    public function getMaximumResultScore()
407
    {
408
        return $this->response->response->maxScore;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
409
    }
410
411
    public function getDebugResponse()
412
    {
413
        return $this->response->debug;
414
    }
415
416
    public function getHighlightedContent()
417
    {
418
        $highlightedContent = false;
419
420
        if ($this->response->highlighting) {
421
            $highlightedContent = $this->response->highlighting;
0 ignored issues
show
Bug introduced by
The property highlighting does not seem to exist in Apache_Solr_Response.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
422
        }
423
424
        return $highlightedContent;
425
    }
426
427
    public function getSpellcheckingSuggestions()
428
    {
429
        $spellcheckingSuggestions = false;
430
431 27
        $suggestions = (array)$this->response->spellcheck->suggestions;
432
433 27
        if (!empty($suggestions)) {
434
            $spellcheckingSuggestions = $suggestions;
435
436
            if (isset($this->response->spellcheck->collations)) {
437
                $collections = (array)$this->response->spellcheck->collations;
438
                $spellcheckingSuggestions['collation'] = $collections['collation'];
439
            }
440
        }
441
442
        return $spellcheckingSuggestions;
443
    }
444
}
445