Failed Conditions
Push — master ( a052b2...ee9c7d )
by Rafael
18:58
created

SolrService::handleErrorResponses()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.0884

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 11
cts 14
cp 0.7856
rs 9.2
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 1
crap 3.0884
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\System\Configuration\TypoScriptConfiguration;
28
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
29
use ApacheSolrForTypo3\Solr\System\Solr\Parser\SchemaParser;
30
use ApacheSolrForTypo3\Solr\System\Solr\Parser\StopWordParser;
31
use ApacheSolrForTypo3\Solr\System\Solr\Parser\SynonymParser;
32
use ApacheSolrForTypo3\Solr\System\Solr\Schema\Schema;
33
use ApacheSolrForTypo3\Solr\System\Solr\SolrCommunicationException;
34
use ApacheSolrForTypo3\Solr\System\Solr\SolrInternalServerErrorException;
35
use ApacheSolrForTypo3\Solr\System\Solr\SolrUnavailableException;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
use Apache_Solr_HttpTransportException;
38
39
/**
40
 * Solr Service Access
41
 *
42
 * @author Ingo Renner <[email protected]>
43
 */
44
class SolrService extends \Apache_Solr_Service
45
{
46
    const LUKE_SERVLET = 'admin/luke';
47
    const SYSTEM_SERVLET = 'admin/system';
48
    const PLUGINS_SERVLET = 'admin/plugins';
49
    const CORES_SERVLET = 'admin/cores';
50
    const SCHEMA_SERVLET = 'schema';
51
    const SYNONYMS_SERVLET = 'schema/analysis/synonyms/';
52
    const STOPWORDS_SERVLET = 'schema/analysis/stopwords/';
53
54
    const SCHEME_HTTP = 'http';
55
    const SCHEME_HTTPS = 'https';
56
57
    /**
58
     * Server connection scheme. http or https.
59
     *
60
     * @var string
61
     */
62
    protected $_scheme = self::SCHEME_HTTP;
63
64
    /**
65
     * Constructed servlet URL for Luke
66
     *
67
     * @var string
68
     */
69
    protected $_lukeUrl;
70
71
    /**
72
     * Constructed servlet URL for plugin information
73
     *
74
     * @var string
75
     */
76
    protected $_pluginsUrl;
77
78
    /**
79
     * @var string
80
     */
81
    protected $_coresUrl;
82
83
    /**
84
     * @var string
85
     */
86
    protected $_extractUrl;
87
88
    /**
89
     * @var string
90
     */
91
    protected $_synonymsUrl;
92
93
    /**
94
     * @var string
95
     */
96
    protected $_stopWordsUrl;
97
98
    /**
99
     * @var string
100
     */
101
    protected $_schemaUrl;
102
103
    /**
104
     * @var bool
105
     */
106
    protected $debug = false;
107
108
    /**
109
     * @var \Apache_Solr_Response
110
     */
111
    protected $responseCache = null;
112
113
    /**
114
     * @var bool
115
     */
116
    protected $hasSearched = false;
117
118
    /**
119
     * @var array
120
     */
121
    protected $lukeData = [];
122
123
    protected $systemData = null;
124
    protected $pluginsData = null;
125
126
    protected $solrconfigName = null;
127
128
    /**
129
     * @var TypoScriptConfiguration
130
     */
131
    protected $configuration;
132
133
    /**
134
     * @var array
135
     */
136
    protected static $pingCache = [];
137
138
    /**
139
     * @var SynonymParser
140
     */
141
    protected $synonymParser = null;
142
143
    /**
144
     * @var StopWordParser
145
     */
146
    protected $stopWordParser = null;
147
148
    /**
149
     * @var SchemaParser
150
     */
151
    protected $schemaParser = null;
152
153
    /**
154
     * @var Schema
155
     */
156
    protected $schema;
157
158
    /**
159
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
160
     */
161
    protected $logger = null;
162
163
    /**
164
     * Constructor
165
     *
166
     * @param string $host Solr host
167
     * @param string $port Solr port
168
     * @param string $path Solr path
169
     * @param string $scheme Scheme, defaults to http, can be https
170
     * @param TypoScriptConfiguration $typoScriptConfiguration
171
     * @param SynonymParser $synonymParser
172
     * @param StopWordParser $stopWordParser
173
     * @param SchemaParser $schemaParser
174
     * @param SolrLogManager $logManager
175
     */
176 119
    public function __construct(
177
        $host = '',
178
        $port = '8983',
179
        $path = '/solr/',
180
        $scheme = 'http',
181
        TypoScriptConfiguration $typoScriptConfiguration = null,
182
        SynonymParser $synonymParser = null,
183
        StopWordParser $stopWordParser = null,
184
        SchemaParser $schemaParser = null,
185
        SolrLogManager $logManager = null
186
    ) {
187
188 119
        $this->setScheme($scheme);
189
190 118
        $this->logger = is_null($logManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $logManager;
191 118
        $this->configuration = is_null($typoScriptConfiguration) ? Util::getSolrConfiguration() : $typoScriptConfiguration;
192 118
        $this->synonymParser = is_null($synonymParser) ? GeneralUtility::makeInstance(SynonymParser::class) : $synonymParser;
193 118
        $this->stopWordParser = is_null($stopWordParser) ? GeneralUtility::makeInstance(StopWordParser::class) : $stopWordParser;
194 118
        $this->schemaParser = is_null($schemaParser) ? GeneralUtility::makeInstance(SchemaParser::class) : $schemaParser;
195
196 118
        $this->initializeTimeoutFromConfiguration();
197
198 118
        parent::__construct($host, $port, $path);
199 118
    }
200
201
    /**
202
     * Initializes the timeout from TypoScript when configuration is present.
203
     *
204
     * @return void
205
     */
206 118
    protected function initializeTimeoutFromConfiguration()
207
    {
208 118
        $timeout = $this->configuration->getSolrTimeout();
209 118
        if ($timeout > 0) {
210 1
            $this->getHttpTransport()->setDefaultTimeout($timeout);
211
        }
212 118
    }
213
214
    /**
215
     * Creates a string representation of the Solr connection. Specifically
216
     * will return the Solr URL.
217
     *
218
     * @return string The Solr URL.
219
     */
220 66
    public function __toString()
221
    {
222 66
        return $this->_scheme . '://' . $this->_host . ':' . $this->_port . $this->_path;
223
    }
224
225
    /**
226
     * Returns the current time in milliseconds.
227
     *
228
     * @return double
229
     */
230 3
    protected function getMilliseconds()
231
    {
232 3
        return GeneralUtility::milliseconds();
233
    }
234
235
    /**
236
     * Performs a search.
237
     *
238
     * @param string $query query string / search term
239
     * @param int $offset result offset for pagination
240
     * @param int $limit number of results to retrieve
241
     * @param array $params additional HTTP GET parameters
242
     * @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST)
243
     * @return \Apache_Solr_Response Solr response
244
     * @throws \RuntimeException if Solr returns a HTTP status code other than 200
245
     */
246 37
    public function search($query, $offset = 0, $limit = 10, $params = [], $method = self::METHOD_GET)
247
    {
248 37
        $response = parent::search($query, $offset, $limit, $params, $method);
249 37
        $this->hasSearched = true;
250
251 37
        $this->responseCache = $response;
252
253 37
        $status = $response->getHttpStatus();
254 37
        $isValidResponse = $status === 200;
255 37
        if ($isValidResponse) {
256 34
            return $response;
257
        }
258
259 3
        $this->handleErrorResponses($response);
260
    }
261
262
    /**
263
     * This method maps the failed solr requests to a meaningful exception.
264
     *
265
     * @param \Apache_Solr_Response $response
266
     * @throws SolrCommunicationException
267
     * @return \Apache_Solr_Response
268
     */
269 3
    protected function handleErrorResponses(\Apache_Solr_Response $response)
270
    {
271 3
        $status = $response->getHttpStatus();
272 3
        $message = $response->getHttpStatusMessage();
273
274 3
        if ($status === 0) {
275 2
            $e = new SolrUnavailableException('Solr Server not available: ' . $message, 1505989391);
276 2
            $e->setSolrResponse($response);
277 2
            throw $e;
278
        }
279
280 1
        if ($status === 500) {
281 1
            $e = new SolrInternalServerErrorException('Internal Server error during search: ' . $message, 1505989897);
282 1
            $e->setSolrResponse($response);
283 1
            throw $e;
284
        }
285
286
        $e = new SolrCommunicationException('Invalid query. Solr returned an error: ' . $status . ' ' . $message, 1293109870);
287
        $e->setSolrResponse($response);
288
289
        throw $e;
290
    }
291
292
    /**
293
     * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
294
     * server is available.
295
     *
296
     * Simply overrides the SolrPhpClient implementation, changing ping from a
297
     * HEAD to a GET request, see http://forge.typo3.org/issues/44167
298
     *
299
     * Also does not report the time, see https://forge.typo3.org/issues/64551
300
     *
301
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
302
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
303
     * @return bool TRUE if Solr can be reached, FALSE if not
304
     */
305 61
    public function ping($timeout = 2, $useCache = true)
306
    {
307 61
        $httpResponse = $this->performPingRequest($timeout, $useCache);
308 61
        return ($httpResponse->getStatusCode() === 200);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $httpResponse->getStatusCode() === 200; (boolean) is incompatible with the return type of the parent method Apache_Solr_Service::ping of type double|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
309
    }
310
311
    /**
312
     * Call the /admin/ping servlet, can be used to get the runtime of a ping request.
313
     *
314
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
315
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
316
     * @return double runtime in milliseconds
317
     * @throws \ApacheSolrForTypo3\Solr\PingFailedException
318
     */
319 3
    public function getPingRoundTripRuntime($timeout = 2, $useCache = true)
320
    {
321 3
        $start = $this->getMilliseconds();
322 3
        $httpResponse = $this->performPingRequest($timeout, $useCache);
323 3
        $end = $this->getMilliseconds();
324
325 3
        if ($httpResponse->getStatusCode() !== 200) {
326 1
            $message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getStatusCode();
327
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
328 1
            $exception = GeneralUtility::makeInstance(PingFailedException::class, $message);
329 1
            $exception->setHttpResponse($httpResponse);
330 1
            throw $exception;
331
        }
332
333 2
        return $end - $start;
334
    }
335
336
    /**
337
     * Performs a ping request and returns the result.
338
     *
339
     * @param int $timeout
340
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
341
     * @return \Apache_Solr_HttpTransport_Response
342
     */
343 64
    protected function performPingRequest($timeout = 2, $useCache = true)
344
    {
345 64
        $cacheKey = (string)($this);
346 64
        if ($useCache && isset(static::$pingCache[$cacheKey])) {
347 53
            return static::$pingCache[$cacheKey];
348
        }
349
350 64
        $pingResult = $this->getHttpTransport()->performGetRequest($this->_pingUrl, $timeout);
351
352 64
        if ($useCache) {
353 63
            static::$pingCache[$cacheKey] = $pingResult;
354
        }
355
356 64
        return $pingResult;
357
    }
358
359
    /**
360
     * Performs a content and meta data extraction request.
361
     *
362
     * @param ExtractingQuery $query An extraction query
363
     * @return array An array containing the extracted content [0] and meta data [1]
364
     */
365 1
    public function extractByQuery(ExtractingQuery $query)
366
    {
367
        $headers = [
368 1
            'Content-Type' => 'multipart/form-data; boundary=' . $query->getMultiPartPostDataBoundary()
369
        ];
370
371
        try {
372 1
            $response = $this->requestServlet(
373 1
                self::EXTRACT_SERVLET,
374 1
                $query->getQueryParameters(),
375 1
                'POST',
376 1
                $headers,
377 1
                $query->getRawPostFileData()
378
            );
379
        } catch (\Exception $e) {
380
            $this->logger->log(
381
                SolrLogManager::ERROR,
382
                'Extracting text and meta data through Solr Cell over HTTP POST',
383
                [
384
                    'query' => (array)$query,
385
                    'parameters' => $query->getQueryParameters(),
386
                    'file' => $query->getFile(),
387
                    'headers' => $headers,
388
                    'query url' => self::EXTRACT_SERVLET,
389
                    'exception' => $e->getMessage()
390
                ]
391
            );
392
        }
393
394
        return [
395 1
            $response->extracted,
396 1
            (array)$response->extracted_metadata
397
        ];
398
    }
399
400
    /**
401
     * Make a request to a servlet (a path) that's not a standard path.
402
     *
403
     * @param string $servlet Path to be added to the base Solr path.
404
     * @param array $parameters Optional, additional request parameters when constructing the URL.
405
     * @param string $method HTTP method to use, defaults to GET.
406
     * @param array $requestHeaders Key value pairs of header names and values. Should include 'Content-Type' for POST and PUT.
407
     * @param string $rawPost Must be an empty string unless method is POST or PUT.
408
     * @param float|bool $timeout Read timeout in seconds, defaults to FALSE.
409
     * @return \Apache_Solr_Response Response object
410
     * @throws \Apache_Solr_HttpTransportException if returned HTTP status is other than 200
411
     */
412 1
    public function requestServlet($servlet, $parameters = [], $method = 'GET', $requestHeaders = [], $rawPost = '', $timeout = false)
413
    {
414
        // Add default parameters
415 1
        $parameters['wt'] = self::SOLR_WRITER;
416 1
        $parameters['json.nl'] = $this->_namedListTreatment;
417 1
        $url = $this->_constructUrl($servlet, $parameters);
418
419 1
        $httpResponse = $this->getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout);
420 1
        $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
421 1
        if ($solrResponse->getHttpStatus() != 200) {
422
            throw new \Apache_Solr_HttpTransportException($solrResponse);
423
        }
424
425 1
        return $solrResponse;
426
    }
427
428
    /**
429
     * Decides which transport method to used, depending on the request method and retrieves the response.
430
     *
431
     * @param string $url
432
     * @param string $method
433
     * @param array $requestHeaders
434
     * @param string $rawPost
435
     * @param float|bool $timeout
436
     * @return \Apache_Solr_HttpTransport_Response
437
     */
438 1
    protected function getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout)
439
    {
440 1
        $httpTransport = $this->getHttpTransport();
441
442 1
        if ($method == self::METHOD_GET) {
443
            return $httpTransport->performGetRequest($url, $timeout);
444
        }
445 1
        if ($method == self::METHOD_POST) {
446
            // FIXME should respect all headers, not only Content-Type
447 1
            return $httpTransport->performPostRequest($url, $rawPost, $requestHeaders['Content-Type'], $timeout);
448
        }
449
450
        throw new \InvalidArgumentException('$method should be GET or POST');
451
    }
452
453
    /**
454
     * Return a valid http URL given this server's scheme, host, port, and path
455
     * and a provided servlet name.
456
     *
457
     * @param string $servlet Servlet name
458
     * @param array $params Additional URL parameters to attach to the end of the URL
459
     * @return string Servlet URL
460
     */
461 118
    protected function _constructUrl($servlet, $params = [])
462
    {
463 118
        $url = parent::_constructUrl($servlet, $params);
464
465 118
        if (!GeneralUtility::isFirstPartOfStr($url, $this->_scheme)) {
466 2
            $parsedUrl = parse_url($url);
467
468
            // unfortunately can't use str_replace as it replace all
469
            // occurrences of $needle and can't be limited to replace only once
470 2
            $url = $this->_scheme . substr($url, strlen($parsedUrl['scheme']));
471
        }
472
473 118
        return $url;
474
    }
475
476
    /**
477
     * Returns the set scheme
478
     *
479
     * @return string
480
     */
481 3
    public function getScheme()
482
    {
483 3
        return $this->_scheme;
484
    }
485
486
    /**
487
     * Set the scheme used. If empty will fallback to constants
488
     *
489
     * @param string $scheme Either http or https
490
     * @throws \UnexpectedValueException
491
     */
492 119
    public function setScheme($scheme)
493
    {
494
        // Use the provided scheme or use the default
495 119
        if (empty($scheme)) {
496 2
            throw new \UnexpectedValueException('Scheme parameter is empty', 1380756390);
497
        }
498
499 118
        $isHttpOrHttps = in_array($scheme, [self::SCHEME_HTTP, self::SCHEME_HTTPS]);
500 118
        if (!$isHttpOrHttps) {
501 1
            throw new \UnexpectedValueException('Unsupported scheme parameter, scheme must be http or https', 1380756442);
502
        }
503
504
        // we have a valid scheme
505 118
        $this->_scheme = $scheme;
506
507 118
        if ($this->_urlsInited) {
508 1
            $this->_initUrls();
509
        }
510 118
    }
511
512
    /**
513
     * get field meta data for the index
514
     *
515
     * @param int $numberOfTerms Number of top terms to fetch for each field
516
     * @return array
517
     */
518
    public function getFieldsMetaData($numberOfTerms = 0)
519
    {
520
        return $this->getLukeMetaData($numberOfTerms)->fields;
521
    }
522
523
    /**
524
     * Retrieves meta data about the index from the luke request handler
525
     *
526
     * @param int $numberOfTerms Number of top terms to fetch for each field
527
     * @return \Apache_Solr_Response Index meta data
528
     */
529
    public function getLukeMetaData($numberOfTerms = 0)
530
    {
531
        if (!isset($this->lukeData[$numberOfTerms])) {
532
            $lukeUrl = $this->_constructUrl(
533
                self::LUKE_SERVLET,
534
                [
535
                    'numTerms' => $numberOfTerms,
536
                    'wt' => self::SOLR_WRITER,
537
                    'fl' => '*'
538
                ]
539
            );
540
541
            $this->lukeData[$numberOfTerms] = $this->_sendRawGet($lukeUrl);
542
        }
543
544
        return $this->lukeData[$numberOfTerms];
545
    }
546
547
    /**
548
     * Central method for making a get operation against this Solr Server
549
     *
550
     * @param string $url
551
     * @param float|bool $timeout Read timeout in seconds
552
     * @return \Apache_Solr_Response
553
     */
554 51
    protected function _sendRawGet($url, $timeout = false)
555
    {
556 51
        $logSeverity = SolrLogManager::INFO;
557 51
        $exception = null;
558
559
        try {
560 51
            $response = parent::_sendRawGet($url, $timeout);
0 ignored issues
show
Bug introduced by
It seems like $timeout defined by parameter $timeout on line 554 can also be of type boolean; however, Apache_Solr_Service::_sendRawGet() does only seem to accept false|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
561 6
        } catch (\Apache_Solr_HttpTransportException $exception) {
562 6
            $logSeverity = SolrLogManager::ERROR;
563 6
            $response = $exception->getResponse();
564
        }
565
566 51
        if ($this->configuration->getLoggingQueryRawGet() || $response->getHttpStatus() != 200) {
567 6
            $this->writeLog($logSeverity, 'Querying Solr using GET', $url, $response, $exception);
568
        }
569
570 51
        return $response;
571
    }
572
573
    /**
574
     * Returns whether a search has been executed or not.
575
     *
576
     * @return bool TRUE if a search has been executed, FALSE otherwise
577
     */
578
    public function hasSearched()
579
    {
580
        return $this->hasSearched;
581
    }
582
583
    /**
584
     * Gets the most recent response (if any)
585
     *
586
     * @return \Apache_Solr_Response Most recent response, or NULL if a search has not been executed yet.
587
     */
588
    public function getResponse()
589
    {
590
        return $this->responseCache;
591
    }
592
593
    /**
594
     * Enable/Disable debug mode
595
     *
596
     * @param bool $debug TRUE to enable debug mode, FALSE to turn off, off by default
597
     */
598
    public function setDebug($debug)
599
    {
600
        $this->debug = (boolean)$debug;
601
    }
602
603
    /**
604
     * Gets information about the plugins installed in Solr
605
     *
606
     * @return array A nested array of plugin data.
607
     */
608 3
    public function getPluginsInformation()
609
    {
610 3
        if (empty($this->pluginsData)) {
611 3
            $pluginsInformation = $this->_sendRawGet($this->_pluginsUrl);
612
613
            // access a random property to trigger response parsing
614 3
            $pluginsInformation->responseHeader;
615 2
            $this->pluginsData = $pluginsInformation;
616
        }
617
618 2
        return $this->pluginsData;
619
    }
620
621
    /**
622
     * Gets information about the Solr server
623
     *
624
     * @return array A nested array of system data.
625
     */
626 5
    public function getSystemInformation()
627
    {
628 5
        if (empty($this->systemData)) {
629 5
            $systemInformation = $this->system();
630
631
            // access a random property to trigger response parsing
632 5
            $systemInformation->responseHeader;
633 4
            $this->systemData = $systemInformation;
634
        }
635
636 4
        return $this->systemData;
637
    }
638
639
    /**
640
     * Gets the name of the solrconfig.xml file installed and in use on the Solr
641
     * server.
642
     *
643
     * @return string Name of the active solrconfig.xml
644
     */
645 3
    public function getSolrconfigName()
646
    {
647 3
        if (is_null($this->solrconfigName)) {
648 3
            $solrconfigXmlUrl = $this->_scheme . '://'
649 3
                . $this->_host . ':' . $this->_port
650 3
                . $this->_path . 'admin/file/?file=solrconfig.xml';
651 3
            $response = $this->_sendRawGet($solrconfigXmlUrl);
652
653 3
            $solrconfigXml = simplexml_load_string($response->getRawResponse());
654 3
            if ($solrconfigXml === false) {
655 1
                throw new \InvalidArgumentException('No valid xml response from schema file: ' . $solrconfigXmlUrl);
656
            }
657 2
            $this->solrconfigName = (string)$solrconfigXml->attributes()->name;
658
        }
659
660 2
        return $this->solrconfigName;
661
    }
662
663
    /**
664
     * Gets the Solr server's version number.
665
     *
666
     * @return string Solr version number
667
     */
668 4
    public function getSolrServerVersion()
669
    {
670 4
        $systemInformation = $this->getSystemInformation();
671
672
        // don't know why $systemInformation->lucene->solr-spec-version won't work
673 3
        $luceneInformation = (array)$systemInformation->lucene;
674 3
        return $luceneInformation['solr-spec-version'];
675
    }
676
677
    /**
678
     * Deletes all index documents of a certain type and does a commit
679
     * afterwards.
680
     *
681
     * @param string $type The type of documents to delete, usually a table name.
682
     * @param bool $commit Will commit immediately after deleting the documents if set, defaults to TRUE
683
     */
684
    public function deleteByType($type, $commit = true)
685
    {
686
        $this->deleteByQuery('type:' . trim($type));
687
688
        if ($commit) {
689
            $this->commit(false, false, false);
690
        }
691
    }
692
693
    /**
694
     * Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be
695
     * a complete and well formed "delete" xml document
696
     *
697
     * @param string $rawPost Expected to be utf-8 encoded xml document
698
     * @param float|int $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
699
     * @return \Apache_Solr_Response
700
     */
701 11
    public function delete($rawPost, $timeout = 3600)
702
    {
703 11
        $response = $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
704
705 11
        $this->logger->log(
706 11
            SolrLogManager::NOTICE,
707 11
            'Delete Query sent.',
708
            [
709 11
                'query' => $rawPost,
710 11
                'query url' => $this->_updateUrl,
711 11
                'response' => (array)$response
712
            ]
713
        );
714
715 11
        return $response;
716
    }
717
718
    /**
719
     * Central method for making a post operation against this Solr Server
720
     *
721
     * @param string $url
722
     * @param string $rawPost
723
     * @param float|bool $timeout Read timeout in seconds
724
     * @param string $contentType
725
     * @return \Apache_Solr_Response
726
     */
727 83
    protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
728
    {
729 83
        $logSeverity = SolrLogManager::INFO;
730 83
        $exception = null;
731
732
        try {
733 83
            $response = parent::_sendRawPost($url, $rawPost, $timeout, $contentType);
0 ignored issues
show
Bug introduced by
It seems like $timeout defined by parameter $timeout on line 727 can also be of type boolean; however, Apache_Solr_Service::_sendRawPost() does only seem to accept false|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
734
        } catch (\Apache_Solr_HttpTransportException $exception) {
735
            $logSeverity = SolrLogManager::ERROR;
736
            $response = $exception->getResponse();
737
        }
738
739 83
        if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) {
740
            $this->writeLog($logSeverity, 'Querying Solr using POST', $url, $response, $exception, $rawPost);
741
        }
742
743 83
        return $response;
744
    }
745
746
    /**
747
     * Get currently configured synonyms
748
     *
749
     * @param string $baseWord If given a base word, retrieves the synonyms for that word only
750
     * @return array
751
     */
752 2
    public function getSynonyms($baseWord = '')
753
    {
754 2
        $this->initializeSynonymsUrl();
755 2
        $synonymsUrl = $this->_synonymsUrl;
756 2
        if (!empty($baseWord)) {
757 2
            $synonymsUrl .= '/' . $baseWord;
758
        }
759
760 2
        $response = $this->_sendRawGet($synonymsUrl);
761 2
        return $this->synonymParser->parseJson($baseWord, $response->getRawResponse());
762
    }
763
764
    /**
765
     * Add list of synonyms for base word to managed synonyms map
766
     *
767
     * @param string $baseWord
768
     * @param array $synonyms
769
     *
770
     * @return \Apache_Solr_Response
771
     *
772
     * @throws \Apache_Solr_InvalidArgumentException If $baseWord or $synonyms are empty
773
     */
774 2
    public function addSynonym($baseWord, array $synonyms)
775
    {
776 2
        $this->initializeSynonymsUrl();
777 2
        $json = $this->synonymParser->toJson($baseWord, $synonyms);
778 2
        return $this->_sendRawPost($this->_synonymsUrl, $json,
779 2
            $this->getHttpTransport()->getDefaultTimeout(), 'application/json');
780
    }
781
782
    /**
783
     * Remove a synonym from the synonyms map
784
     *
785
     * @param string $baseWord
786
     * @return \Apache_Solr_Response
787
     * @throws \Apache_Solr_InvalidArgumentException
788
     */
789 2
    public function deleteSynonym($baseWord)
790
    {
791 2
        $this->initializeSynonymsUrl();
792 2
        if (empty($baseWord)) {
793
            throw new \Apache_Solr_InvalidArgumentException('Must provide base word.');
794
        }
795
796 2
        return $this->_sendRawDelete($this->_synonymsUrl . '/' . urlencode($baseWord));
797
    }
798
799
    /**
800
     * Central method for making a HTTP DELETE operation against the Solr server
801
     *
802
     * @param string $url
803
     * @param bool|float $timeout Read timeout in seconds
804
     * @return \Apache_Solr_Response
805
     */
806 4
    protected function _sendRawDelete($url, $timeout = false)
807
    {
808 4
        $logSeverity = SolrLogManager::INFO;
809 4
        $exception = null;
810
811
        try {
812 4
            $httpTransport = $this->getHttpTransport();
813 4
            $httpResponse = $httpTransport->performDeleteRequest($url, $timeout);
814 4
            $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
815
816 4
            if ($solrResponse->getHttpStatus() != 200) {
817 4
                throw new \Apache_Solr_HttpTransportException($solrResponse);
818
            }
819 4
        } catch (\Apache_Solr_HttpTransportException $exception) {
820 4
            $logSeverity = SolrLogManager::ERROR;
821 4
            $solrResponse = $exception->getResponse();
822
        }
823
824 4
        if ($this->configuration->getLoggingQueryRawDelete() || $solrResponse->getHttpStatus() != 200) {
825 4
            $this->writeLog($logSeverity, 'Querying Solr using DELETE', $url, $solrResponse, $exception);
826
        }
827
828 4
        return $solrResponse;
829
    }
830
831
    /**
832
     * Build the log data and writes the message to the log
833
     *
834
     * @param integer $logSeverity
835
     * @param string $message
836
     * @param string $url
837
     * @param \Apache_Solr_Response $solrResponse
838
     * @param \Exception $exception
839
     * @param string $contentSend
840
     */
841 8
    protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '')
842
    {
843 8
        $logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend);
844 8
        $this->logger->log($logSeverity, $message, $logData);
845 8
    }
846
847
    /**
848
     * Parses the solr information to build data for the logger.
849
     *
850
     * @param \Apache_Solr_Response $solrResponse
851
     * @param \Exception $e
852
     * @param string $url
853
     * @param string $contentSend
854
     * @return array
855
     */
856 8
    protected function buildLogDataFromResponse(\Apache_Solr_Response $solrResponse, \Exception $e = null, $url = '', $contentSend = '')
857
    {
858 8
        $logData = ['query url' => $url, 'response' => (array)$solrResponse];
859
860 8
        if ($contentSend !== '') {
861
            $logData['content'] = $contentSend;
862
        }
863
864 8
        if (!empty($e)) {
865 8
            $logData['exception'] = $e->__toString();
866 8
            return $logData;
867
        } else {
868
            // trigger data parsing
869
            $solrResponse->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...
870
            $logData['response data'] = print_r($solrResponse, true);
871
            return $logData;
872
        }
873
    }
874
875
    /**
876
     * Get currently configured stop words
877
     *
878
     * @return array
879
     */
880 3
    public function getStopWords()
881
    {
882 3
        $this->initializeStopWordsUrl();
883 3
        $response = $this->_sendRawGet($this->_stopWordsUrl);
884 3
        return $this->stopWordParser->parseJson($response->getRawResponse());
885
    }
886
887
    /**
888
     * Adds stop words to the managed stop word list
889
     *
890
     * @param array|string $stopWords string for a single word, array for multiple words
891
     * @return \Apache_Solr_Response
892
     * @throws \Apache_Solr_InvalidArgumentException If $stopWords is empty
893
     */
894 2
    public function addStopWords($stopWords)
895
    {
896 2
        $this->initializeStopWordsUrl();
897 2
        $json = $this->stopWordParser->toJson($stopWords);
898 2
        return $this->_sendRawPost($this->_stopWordsUrl, $json,
899 2
            $this->getHttpTransport()->getDefaultTimeout(), 'application/json');
900
    }
901
902
    /**
903
     * Deletes a words from the managed stop word list
904
     *
905
     * @param string $stopWord stop word to delete
906
     * @return \Apache_Solr_Response
907
     * @throws \Apache_Solr_InvalidArgumentException If $stopWords is empty
908
     */
909 2
    public function deleteStopWord($stopWord)
910
    {
911 2
        $this->initializeStopWordsUrl();
912 2
        if (empty($stopWord)) {
913
            throw new \Apache_Solr_InvalidArgumentException('Must provide stop word.');
914
        }
915
916 2
        return $this->_sendRawDelete($this->_stopWordsUrl . '/' . urlencode($stopWord));
917
    }
918
919
    /**
920
     * Returns the core name from the configured path.
921
     *
922
     * @return string
923
     */
924 3
    public function getCoreName()
925
    {
926 3
        $paths = explode('/', trim($this->_path, '/'));
927
928 3
        return (string)array_pop($paths);
929
    }
930
931
    /**
932
     * Returns the core name from the configured path without the core name.
933
     *
934
     * @return string
935
     */
936 118
    public function getCoreBasePath()
937
    {
938 118
        $pathWithoutLeadingAndTrailingSlashes = trim(trim($this->_path), "/");
939 118
        $pathWithoutLastSegment = substr($pathWithoutLeadingAndTrailingSlashes, 0, strrpos($pathWithoutLeadingAndTrailingSlashes, "/"));
940 118
        return '/' . $pathWithoutLastSegment . '/';
941
    }
942
943
    /**
944
     * Reloads the current core
945
     *
946
     * @return \Apache_Solr_Response
947
     */
948 1
    public function reloadCore()
949
    {
950 1
        return $this->reloadCoreByName($this->getCoreName());
951
    }
952
953
    /**
954
     * Reloads a core of the connection by a given corename.
955
     *
956
     * @param string $coreName
957
     * @return \Apache_Solr_Response
958
     */
959 1
    public function reloadCoreByName($coreName)
960
    {
961 1
        $coreAdminReloadUrl = $this->_coresUrl . '?action=reload&core=' . $coreName;
962 1
        return $this->_sendRawGet($coreAdminReloadUrl);
963
    }
964
965
    /**
966
     * initializes various URLs, including the Luke URL
967
     *
968
     * @return void
969
     */
970 118
    protected function _initUrls()
971
    {
972 118
        parent::_initUrls();
973
974 118
        $this->_lukeUrl = $this->_constructUrl(
975 118
            self::LUKE_SERVLET,
976
            [
977 118
                'numTerms' => '0',
978 118
                'wt' => self::SOLR_WRITER
979
            ]
980
        );
981
982 118
        $this->_pluginsUrl = $this->_constructUrl(
983 118
            self::PLUGINS_SERVLET,
984 118
            ['wt' => self::SOLR_WRITER]
985
        );
986
987 118
        $this->_coresUrl =
988 118
            $this->_scheme . '://' .
989 118
            $this->_host . ':' .
990 118
            $this->_port .
991 118
            $this->getCoreBasePath() .
992 118
            self::CORES_SERVLET;
993
994 118
        $this->_schemaUrl = $this->_constructUrl(self::SCHEMA_SERVLET);
995 118
    }
996
997
    /**
998
     * @return void
999
     */
1000 2
    protected function initializeSynonymsUrl()
1001
    {
1002 2
        if (trim($this->_synonymsUrl) !== '') {
1003 2
            return;
1004
        }
1005 2
        $this->_synonymsUrl = $this->_constructUrl(self::SYNONYMS_SERVLET) . $this->getSchema()->getLanguage();
1006 2
    }
1007
1008
    /**
1009
     * @return void
1010
     */
1011 3
    protected function initializeStopWordsUrl()
1012
    {
1013 3
        if (trim($this->_stopWordsUrl) !== '') {
1014 2
            return;
1015
        }
1016
1017 3
        $this->_stopWordsUrl = $this->_constructUrl(self::STOPWORDS_SERVLET) . $this->getSchema()->getLanguage();
1018 3
    }
1019
1020
    /**
1021
     * Get the configured schema for the current core.
1022
     *
1023
     * @return Schema
1024
     */
1025 8
    public function getSchema()
1026
    {
1027 8
        if ($this->schema !== null) {
1028
            return $this->schema;
1029
        }
1030 8
        $response = $this->_sendRawGet($this->_schemaUrl);
1031 8
        $this->schema = $this->schemaParser->parseJson($response->getRawResponse());
1032 8
        return $this->schema;
1033
    }
1034
}
1035