Passed
Push — master ( 20d9ae...2c204f )
by Timo
01:45
created

Search::search()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 52
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 5.2

Importance

Changes 0
Metric Value
dl 0
loc 52
ccs 24
cts 30
cp 0.8
rs 8.6868
c 0
b 0
f 0
cc 5
eloc 33
nc 16
nop 3
crap 5.2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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