Passed
Push — master ( 0ba0e3...644d9b )
by Timo
19:06
created

Search::getRawResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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\Search\FacetsModifier;
28
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use TYPO3\CMS\Core\SingletonInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
/**
34
 * Class to handle solr search requests
35
 *
36
 * @author Ingo Renner <[email protected]>
37
 */
38
class Search
39
{
40
41
    /**
42
     * An instance of the Solr service
43
     *
44
     * @var SolrService
45
     */
46
    protected $solr = null;
47
48
    /**
49
     * The search query
50
     *
51
     * @var Query
52
     */
53
    protected $query = null;
54
55
    /**
56
     * The search response
57
     *
58
     * @var \Apache_Solr_Response
59
     */
60
    protected $response = null;
61
62
    /**
63
     * Flag for marking a search
64
     *
65
     * @var bool
66
     */
67
    protected $hasSearched = false;
68
69
    /**
70
     * @var TypoScriptConfiguration
71
     */
72
    protected $configuration;
73
74
    // TODO Override __clone to reset $response and $hasSearched
75
76
    /**
77
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
78
     */
79
    protected $logger = null;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param SolrService $solrConnection The Solr connection to use for searching
85
     */
86
    public function __construct(SolrService $solrConnection = null)
87
    {
88
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
89 36
90
        $this->solr = $solrConnection;
91 36
92
        if (is_null($solrConnection)) {
93 36
            /** @var $connectionManager ConnectionManager */
94
            $connectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
95 36
            $this->solr = $connectionManager->getConnectionByPageId($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sys_language_uid);
96
        }
97 1
98 1
        $this->configuration = Util::getSolrConfiguration();
99
    }
100
101 36
    /**
102 36
     * Gets the Solr connection used by this search.
103
     *
104
     * @return SolrService Solr connection
105
     */
106
    public function getSolrConnection()
107
    {
108
        return $this->solr;
109
    }
110
111
    /**
112
     * Sets the Solr connection used by this search.
113
     *
114
     * Since ApacheSolrForTypo3\Solr\Search is a \TYPO3\CMS\Core\SingletonInterface, this is needed to
115
     * be able to switch between multiple cores/connections during
116
     * one request
117
     *
118
     * @param SolrService $solrConnection
119
     */
120
    public function setSolrConnection(SolrService $solrConnection)
121
    {
122
        $this->solr = $solrConnection;
123
    }
124
125
    /**
126
     * Executes a query against a Solr server.
127
     *
128
     * 1) Gets the query string
129
     * 2) Conducts the actual search
130
     * 3) Checks debug settings
131
     *
132
     * @param Query $query The query with keywords, filters, and so on.
133
     * @param int $offset Result offset for pagination.
134
     * @param int $limit Maximum number of results to return. If set to NULL, this value is taken from the query object.
135
     * @return \Apache_Solr_Response Solr response
136
     */
137
    public function search(Query $query, $offset = 0, $limit = 10)
138
    {
139
        $this->query = $query;
140 30
141
        if (empty($limit)) {
142 30
            $limit = $query->getResultsPerPage();
143 30
        }
144
145 30
        try {
146 26
            $response = $this->solr->search(
147
                $query->getQueryString(),
148
                $offset,
149
                $limit,
150 30
                $query->getQueryParameters()
151 30
            );
152
153
            if ($this->configuration->getLoggingQueryQueryString()) {
154 30
                $this->logger->log(
155
                    SolrLogManager::INFO,
156
                    'Querying Solr, getting result',
157 29
                    [
158
                        'query string' => $query->getQueryString(),
159
                        'query parameters' => $query->getQueryParameters(),
160
                        'response' => json_decode($response->getRawResponse(),
161
                            true)
162
                    ]
163
                );
164
            }
165 29
        } catch (\RuntimeException $e) {
166
            $response = $this->solr->getResponse();
167
168
            if ($this->configuration->getLoggingExceptions()) {
169 1
                $this->logger->log(
170 1
                    SolrLogManager::ERROR,
171
                    'Exception while querying Solr',
172 1
                    [
173 1
                        'exception' => $e->__toString(),
174 1
                        'query' => (array)$query,
175 1
                        'offset' => $offset,
176
                        'limit' => $limit
177 1
                    ]
178 1
                );
179 1
            }
180 1
        }
181
182
        $this->response = $response;
183
        $this->hasSearched = true;
184
185
        return $this->response;
186 30
    }
187 30
188 30
    /**
189
     * Sends a ping to the solr server to see whether it is available.
190 30
     *
191
     * @param bool $useCache Set to true if the cache should be used.
192
     * @return bool Returns TRUE on successful ping.
193
     * @throws \Exception Throws an exception in case ping was not successful.
194
     */
195
    public function ping($useCache = true)
196
    {
197
        $solrAvailable = false;
198
199 30
        try {
200
            if (!$this->solr->ping(2, $useCache)) {
201
                throw new \Exception('Solr Server not responding.', 1237475791);
202 30
            }
203 26
204 26
            $solrAvailable = true;
205
        } catch (\Exception $e) {
206 26
            if ($this->configuration->getLoggingExceptions()) {
207 26
                $this->logger->log(
208
                    SolrLogManager::ERROR,
209
                    'Exception while trying to ping the solr server',
210
                    [
211 26
                        $e->__toString()
212
                    ]
213
                );
214
            }
215 26
        }
216
217
        return $solrAvailable;
218
    }
219
220
    /**
221 30
     * checks whether a search has been executed
222
     *
223
     * @return bool    TRUE if there was a search, FALSE otherwise (if the user just visited the search page f.e.)
224
     */
225
    public function hasSearched()
226
    {
227
        return $this->hasSearched;
228
    }
229
230
    /**
231
     * Gets the query object.
232 30
     *
233
     * @return Query Query
234
     */
235 30
    public function getQuery()
236
    {
237
        return $this->query;
238
    }
239
240
    /**
241
     * Gets the Solr response
242
     *
243
     * @return \Apache_Solr_Response
244
     */
245
    public function getResponse()
246
    {
247
        return $this->response;
248
    }
249
250
    public function getRawResponse()
251
    {
252
        return $this->response->getRawResponse();
253
    }
254
255
    public function getResponseHeader()
256
    {
257 30
        return $this->getResponse()->responseHeader;
258
    }
259
260
    public function getResponseBody()
261
    {
262
        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...
263
    }
264
265
    /**
266
     * Returns all results documents raw. Use with caution!
267 25
     *
268
     * @return \Apache_Solr_Document[]
269 25
     */
270
    public function getResultDocumentsRaw()
271
    {
272 25
        return $this->getResponseBody()->docs;
273
    }
274
275
    /**
276 25
     * Returns all result documents but applies htmlspecialchars() on all fields retrieved
277
     * from solr except the configured fields in plugin.tx_solr.search.trustedFields
278
     *
279
     * @return \Apache_Solr_Document[]
280
     */
281
    public function getResultDocumentsEscaped()
282
    {
283
        return $this->applyHtmlSpecialCharsOnAllFields($this->getResponseBody()->docs);
284
    }
285
286
    /**
287
     * This method is used to apply htmlspecialchars on all document fields that
288
     * are not configured to be secure. Secure mean that we know where the content is coming from.
289 25
     *
290
     * @param array $documents
291
     * @return \Apache_Solr_Document[]
292
     */
293
    protected function applyHtmlSpecialCharsOnAllFields(array $documents)
294
    {
295
        $trustedSolrFields = $this->configuration->getSearchTrustedFieldsArray();
296
297 25
        foreach ($documents as $key => $document) {
298
            $fieldNames = $document->getFieldNames();
299 25
300
            foreach ($fieldNames as $fieldName) {
301
                if (in_array($fieldName, $trustedSolrFields)) {
302
                    // we skip this field, since it was marked as secure
303
                    continue;
304
                }
305
306
                $document->{$fieldName} = $this->applyHtmlSpecialCharsOnSingleFieldValue($document->{$fieldName});
307 22
            }
308
309 22
            $documents[$key] = $document;
310
        }
311
312
        return $documents;
313
    }
314
315
    /**
316
     * Applies htmlspecialchars on all items of an array of a single value.
317 23
     *
318
     * @param $fieldValue
319 23
     * @return array|string
320
     */
321
    protected function applyHtmlSpecialCharsOnSingleFieldValue($fieldValue)
322
    {
323
        if (is_array($fieldValue)) {
324
            foreach ($fieldValue as $key => $fieldValueItem) {
325
                $fieldValue[$key] = htmlspecialchars($fieldValueItem, null, null, false);
326
            }
327 20
        } else {
328
            $fieldValue = htmlspecialchars($fieldValue, null, null, false);
329 20
        }
330
331
        return $fieldValue;
332 23
    }
333
334 23
    /**
335
     * Gets the time Solr took to execute the query and return the result.
336
     *
337
     * @return int Query time in milliseconds
338
     */
339
    public function getQueryTime()
340
    {
341
        return $this->getResponseHeader()->QTime;
342 22
    }
343
344 22
    /**
345
     * Gets the number of results per page.
346
     *
347
     * @return int Number of results per page
348
     */
349
    public function getResultsPerPage()
350
    {
351
        return $this->getResponseHeader()->params->rows;
352
    }
353 3
354
    /**
355 3
     * Gets all facets with their fields, options, and counts.
356
     *
357
     * @return array
358
     */
359
    public function getFacetCounts()
360
    {
361
        static $facetCountsModified = false;
362
        static $facetCounts = null;
363
364
        $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...
365 3
366
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyFacets'])) {
367 3
            if (!$facetCountsModified) {
368
                $facetCounts = $unmodifiedFacetCounts;
369 3
370 3
                foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyFacets'] as $classReference) {
371
                    $facetsModifier = GeneralUtility::getUserObj($classReference);
372 3
373 3
                    if ($facetsModifier instanceof FacetsModifier) {
374
                        $facetCounts = $facetsModifier->modifyFacets($facetCounts);
375 3
                        $facetCountsModified = true;
376
                    } else {
377
                        throw new \UnexpectedValueException(
378 3
                            get_class($facetsModifier) . ' must implement interface ' . FacetsModifier::class,
379
                            1310387526
380
                        );
381 3
                    }
382
                }
383
            }
384 3
        } else {
385
            $facetCounts = $unmodifiedFacetCounts;
386
        }
387
388
        return $facetCounts;
389
    }
390
391
    public function getFacetFieldOptions($facetField)
392
    {
393 3
        $facetOptions = null;
394
395 3
        if (property_exists($this->getFacetCounts()->facet_fields,
396 3
            $facetField)) {
397 3
            $facetOptions = get_object_vars($this->getFacetCounts()->facet_fields->$facetField);
398
        }
399
400 3
        return $facetOptions;
401
    }
402
403 3
    public function getFacetQueryOptions($facetField)
404
    {
405
        $options = [];
406
407
        $facetQueries = get_object_vars($this->getFacetCounts()->facet_queries);
408
        foreach ($facetQueries as $facetQuery => $numberOfResults) {
409
            // remove tags from the facet.query response, for facet.field
410
            // and facet.range Solr does that on its own automatically
411 20
            $facetQuery = preg_replace('/^\{!ex=[^\}]*\}(.*)/', '\\1',
412
                $facetQuery);
413 20
414
            if (GeneralUtility::isFirstPartOfStr($facetQuery, $facetField)) {
415
                $options[$facetQuery] = $numberOfResults;
416
            }
417
        }
418
419
        // filter out queries with no results
420
        $options = array_filter($options);
421
422
        return $options;
423
    }
424
425
    public function getFacetRangeOptions($rangeFacetField)
426
    {
427
        return get_object_vars($this->getFacetCounts()->facet_ranges->$rangeFacetField);
428
    }
429
430
    public function getNumberOfResults()
431
    {
432
        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...
433
    }
434
435
    /**
436
     * Gets the result offset.
437
     *
438
     * @return int Result offset
439
     */
440
    public function getResultOffset()
441
    {
442
        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...
443
    }
444
445
    public function getMaximumResultScore()
446
    {
447
        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...
448
    }
449
450
    public function getDebugResponse()
451
    {
452
        return $this->response->debug;
453
    }
454
455
    public function getHighlightedContent()
456
    {
457
        $highlightedContent = false;
458
459
        if ($this->response->highlighting) {
460
            $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...
461
        }
462
463
        return $highlightedContent;
464
    }
465
466
    public function getSpellcheckingSuggestions()
467
    {
468
        $spellcheckingSuggestions = false;
469
470
        $suggestions = (array)$this->response->spellcheck->suggestions;
471
472
        if (!empty($suggestions)) {
473
            $spellcheckingSuggestions = $suggestions;
474
475
            if (isset($this->response->spellcheck->collations)) {
476
                $collections = (array)$this->response->spellcheck->collations;
477
                $spellcheckingSuggestions['collation'] = $collections['collation'];
478
            }
479
        }
480
481
        return $spellcheckingSuggestions;
482
    }
483
}
484