Passed
Push — release-11.5.x ( 1c345b...71e6eb )
by Markus
26:38
created

SolrAdminService::getSystemProperties()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
ccs 0
cts 8
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 0
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace ApacheSolrForTypo3\Solr\System\Solr\Service;
19
20
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
21
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
22
use ApacheSolrForTypo3\Solr\System\Solr\Parser\SchemaParser;
23
use ApacheSolrForTypo3\Solr\System\Solr\Parser\StopWordParser;
24
use ApacheSolrForTypo3\Solr\System\Solr\Parser\SynonymParser;
25
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
26
use ApacheSolrForTypo3\Solr\System\Solr\Schema\Schema;
27
use InvalidArgumentException;
28
use function simplexml_load_string;
29
use Solarium\Client;
30
use stdClass;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
/**
34
 * Class SolrAdminService
35
 */
36
class SolrAdminService extends AbstractSolrService
37
{
38
    const PLUGINS_SERVLET = 'admin/plugins';
39
    const LUKE_SERVLET = 'admin/luke';
40
    const SYSTEM_SERVLET = 'admin/system';
41
    const CORES_SERVLET = '../admin/cores';
42
    const PROPERTY_SERVLET = '../admin/info/properties';
43
    const FILE_SERVLET = 'admin/file';
44
    const SCHEMA_SERVLET = 'schema';
45
    const SYNONYMS_SERVLET = 'schema/analysis/synonyms/';
46
    const STOPWORDS_SERVLET = 'schema/analysis/stopwords/';
47
48
    /**
49
     * @var array
50
     */
51
    protected array $lukeData = [];
52
53
    /**
54
     * @var ResponseAdapter|null
55
     */
56
    protected ?ResponseAdapter $systemData = null;
57
58
    /**
59
     * @var ResponseAdapter|null
60
     */
61
    protected ?ResponseAdapter $pluginsData = null;
62
63
    /**
64
     * @var string|null
65
     */
66
    protected ?string $solrconfigName = null;
67
68
    /**
69
     * @var SchemaParser
70
     */
71
    protected SchemaParser $schemaParser;
72
73
    /**
74
     * @var Schema|null
75
     */
76
    protected ?Schema $schema = null;
77
78
    /**
79
     * @var string
80
     */
81
    protected string $_synonymsUrl = '';
82
83
    /**
84
     * @var string
85
     */
86
    protected string $_stopWordsUrl = '';
87
88
    /**
89
     * @var SynonymParser
90
     */
91
    protected SynonymParser $synonymParser;
92
93
    /**
94
     * @var StopWordParser
95
     */
96
    protected StopWordParser $stopWordParser;
97
98
    /**
99
     * Constructor
100
     *
101
     * @param Client $client
102
     * @param TypoScriptConfiguration|null $typoScriptConfiguration
103
     * @param SolrLogManager|null $logManager
104
     * @param SynonymParser|null $synonymParser
105
     * @param StopWordParser|null $stopWordParser
106
     * @param SchemaParser|null $schemaParser
107
     */
108 30
    public function __construct(
109
        Client $client,
110
        TypoScriptConfiguration $typoScriptConfiguration = null,
111
        SolrLogManager $logManager = null,
112
        SynonymParser $synonymParser = null,
113
        StopWordParser $stopWordParser = null,
114
        SchemaParser $schemaParser = null
115
    ) {
116 30
        parent::__construct($client, $typoScriptConfiguration, $logManager);
117
118 30
        $this->synonymParser = $synonymParser ?? GeneralUtility::makeInstance(SynonymParser::class);
119 30
        $this->stopWordParser = $stopWordParser ?? GeneralUtility::makeInstance(StopWordParser::class);
120 30
        $this->schemaParser = $schemaParser ?? GeneralUtility::makeInstance(SchemaParser::class);
121
    }
122
123
    /**
124
     * Call the /admin/system servlet and retrieve system information about Solr
125
     *
126
     * @return ResponseAdapter
127
     */
128 7
    public function system(): ResponseAdapter
129
    {
130 7
        return $this->_sendRawGet($this->_constructUrl(self::SYSTEM_SERVLET, ['wt' => 'json']));
131
    }
132
133
    /**
134
     * Gets system properties
135
     *
136
     * @return array|null
137
     */
138
    public function getSystemProperties(): ?array
139
    {
140
        $url = $this->_constructUrl(self::PROPERTY_SERVLET, ['wt' => 'json']);
141
        $propertyInformation = $this->_sendRawGet($url);
142
143
        $parsedPropertyInformation = $propertyInformation->getParsedData();
144
        if ($parsedPropertyInformation === null
145
            || !property_exists($parsedPropertyInformation, 'system.properties')
146
        ) {
147
            return null;
148
        }
149
150
        return (array)$parsedPropertyInformation->{'system.properties'};
151
    }
152
153
    /**
154
     * Gets information about the plugins installed in Solr
155
     *
156
     * @return ResponseAdapter|null A nested array of plugin data.
157
     */
158 5
    public function getPluginsInformation(): ?ResponseAdapter
159
    {
160 5
        if (count($this->pluginsData ?? []) === 0) {
161 5
            $url = $this->_constructUrl(self::PLUGINS_SERVLET, ['wt' => 'json']);
162 5
            $pluginsInformation = $this->_sendRawGet($url);
163
164
            /**
165
             * access a random property to trigger response parsing
166
             * @noinspection PhpExpressionResultUnusedInspection
167
             */
168 5
            $pluginsInformation->responseHeader;
169 5
            $this->pluginsData = $pluginsInformation;
170
        }
171
172 5
        return $this->pluginsData;
173
    }
174
175
    /**
176
     * get field meta data for the index
177
     *
178
     * @param int $numberOfTerms Number of top terms to fetch for each field
179
     * @return stdClass
180
     */
181
    public function getFieldsMetaData(int $numberOfTerms = 0): stdClass
182
    {
183
        return $this->getLukeMetaData($numberOfTerms)->fields;
184
    }
185
186
    /**
187
     * Retrieves metadata about the index from the luke request handler
188
     *
189
     * @param int $numberOfTerms Number of top terms to fetch for each field
190
     * @return ResponseAdapter Index meta data
191
     */
192 1
    public function getLukeMetaData(int $numberOfTerms = 0): ResponseAdapter
193
    {
194 1
        if (!isset($this->lukeData[$numberOfTerms])) {
195 1
            $lukeUrl = $this->_constructUrl(
196 1
                self::LUKE_SERVLET,
197 1
                ['numTerms' => $numberOfTerms, 'wt' => 'json', 'fl' => '*']
198 1
            );
199
200 1
            $this->lukeData[$numberOfTerms] = $this->_sendRawGet($lukeUrl);
201
        }
202
203 1
        return $this->lukeData[$numberOfTerms];
204
    }
205
206
    /**
207
     * Gets information about the Solr server
208
     *
209
     * @return ResponseAdapter
210
     */
211 7
    public function getSystemInformation(): ResponseAdapter
212
    {
213 7
        if (empty($this->systemData)) {
214 7
            $systemInformation = $this->system();
215
216
            /**
217
             * access a random property to trigger response parsing
218
             * @noinspection PhpExpressionResultUnusedInspection
219
             */
220 7
            $systemInformation->responseHeader;
221 7
            $this->systemData = $systemInformation;
222
        }
223
224 7
        return $this->systemData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->systemData could return the type null which is incompatible with the type-hinted return ApacheSolrForTypo3\Solr\...em\Solr\ResponseAdapter. Consider adding an additional type-check to rule them out.
Loading history...
225
    }
226
227
    /**
228
     * Gets the name of the solrconfig.xml file installed and in use on the Solr
229
     * server.
230
     *
231
     * @return string Name of the active solrconfig.xml
232
     */
233 4
    public function getSolrconfigName(): ?string
234
    {
235 4
        if (is_null($this->solrconfigName)) {
236 4
            $solrconfigXmlUrl = $this->_constructUrl(self::FILE_SERVLET, ['file' => 'solrconfig.xml']);
237 4
            $response = $this->_sendRawGet($solrconfigXmlUrl);
238 4
            $solrconfigXml = simplexml_load_string($response->getRawResponse());
0 ignored issues
show
Bug introduced by
It seems like $response->getRawResponse() can also be of type null; however, parameter $data of simplexml_load_string() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

238
            $solrconfigXml = simplexml_load_string(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
239 4
            if ($solrconfigXml === false) {
240 1
                throw new InvalidArgumentException('No valid xml response from schema file: ' . $solrconfigXmlUrl);
241
            }
242 3
            $this->solrconfigName = (string)$solrconfigXml->attributes()->name;
243
        }
244
245 3
        return $this->solrconfigName;
246
    }
247
248
    /**
249
     * Gets the Solr server's version number.
250
     *
251
     * @return string Solr version number
252
     */
253 5
    public function getSolrServerVersion(): string
254
    {
255 5
        $systemInformation = $this->getSystemInformation();
256
        // don't know why $systemInformation->lucene->solr-spec-version won't work
257 5
        $luceneInformation = (array)$systemInformation->lucene;
258 5
        return $luceneInformation['solr-spec-version'] ?? '';
259
    }
260
261
    /**
262
     * Reloads the current core
263
     *
264
     * @return ResponseAdapter
265
     */
266 11
    public function reloadCore(): ResponseAdapter
267
    {
268 11
        return $this->reloadCoreByName($this->getPrimaryEndpoint()->getCore());
0 ignored issues
show
Bug introduced by
It seems like $this->getPrimaryEndpoint()->getCore() can also be of type null; however, parameter $coreName of ApacheSolrForTypo3\Solr\...ice::reloadCoreByName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
        return $this->reloadCoreByName(/** @scrutinizer ignore-type */ $this->getPrimaryEndpoint()->getCore());
Loading history...
269
    }
270
271
    /**
272
     * Reloads a core of the connection by a given core-name.
273
     *
274
     * @param string $coreName
275
     * @return ResponseAdapter
276
     */
277 11
    public function reloadCoreByName(string $coreName): ResponseAdapter
278
    {
279 11
        $coreAdminReloadUrl = $this->_constructUrl(self::CORES_SERVLET) . '?action=reload&core=' . $coreName;
280 11
        return $this->_sendRawGet($coreAdminReloadUrl);
281
    }
282
283
    /**
284
     * Get the configured schema for the current core.
285
     *
286
     * @return Schema
287
     */
288 14
    public function getSchema(): Schema
289
    {
290 14
        if ($this->schema !== null) {
291 3
            return $this->schema;
292
        }
293 14
        $response = $this->_sendRawGet($this->_constructUrl(self::SCHEMA_SERVLET));
294
295 14
        $this->schema = $this->schemaParser->parseJson($response->getRawResponse());
0 ignored issues
show
Bug introduced by
It seems like $response->getRawResponse() can also be of type null; however, parameter $jsonString of ApacheSolrForTypo3\Solr\...hemaParser::parseJson() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
        $this->schema = $this->schemaParser->parseJson(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
296 14
        return $this->schema;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->schema returns the type null which is incompatible with the type-hinted return ApacheSolrForTypo3\Solr\System\Solr\Schema\Schema.
Loading history...
297
    }
298
299
    /**
300
     * Get currently configured synonyms
301
     *
302
     * @param string $baseWord If given a base word, retrieves the synonyms for that word only
303
     * @return array
304
     */
305 7
    public function getSynonyms(string $baseWord = ''): array
306
    {
307 7
        $this->initializeSynonymsUrl();
308 7
        $synonymsUrl = $this->_synonymsUrl;
309 7
        if (!empty($baseWord)) {
310 7
            $synonymsUrl .= '/' . rawurlencode(rawurlencode($baseWord));
311
        }
312
313 7
        $response = $this->_sendRawGet($synonymsUrl);
314 7
        return $this->synonymParser->parseJson($baseWord, $response->getRawResponse());
0 ignored issues
show
Bug introduced by
It seems like $response->getRawResponse() can also be of type null; however, parameter $jsonString of ApacheSolrForTypo3\Solr\...onymParser::parseJson() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

314
        return $this->synonymParser->parseJson($baseWord, /** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
315
    }
316
317
    /**
318
     * Add list of synonyms for base word to managed synonyms map
319
     *
320
     * @param string $baseWord
321
     * @param array $synonyms
322
     *
323
     * @return ResponseAdapter
324
     */
325 7
    public function addSynonym(string $baseWord, array $synonyms): ResponseAdapter
326
    {
327 7
        $this->initializeSynonymsUrl();
328 7
        $json = $this->synonymParser->toJson($baseWord, $synonyms);
329 7
        return $this->_sendRawPost($this->_synonymsUrl, $json, 'application/json');
330
    }
331
332
    /**
333
     * Remove a synonym from the synonyms map
334
     *
335
     * @param string $baseWord
336
     * @return ResponseAdapter
337
     */
338 7
    public function deleteSynonym(string $baseWord): ResponseAdapter
339
    {
340 7
        $this->initializeSynonymsUrl();
341 7
        return $this->_sendRawDelete($this->_synonymsUrl . '/' . rawurlencode(rawurlencode($baseWord)));
342
    }
343
344
    /**
345
     * Get currently configured stop words
346
     *
347
     * @return array
348
     */
349 3
    public function getStopWords(): array
350
    {
351 3
        $this->initializeStopWordsUrl();
352 3
        $response = $this->_sendRawGet($this->_stopWordsUrl);
353 3
        return $this->stopWordParser->parseJson($response->getRawResponse());
0 ignored issues
show
Bug introduced by
It seems like $response->getRawResponse() can also be of type null; however, parameter $jsonString of ApacheSolrForTypo3\Solr\...WordParser::parseJson() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

353
        return $this->stopWordParser->parseJson(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
354
    }
355
356
    /**
357
     * Adds stop words to the managed stop word list
358
     *
359
     * @param array|string $stopWords string for a single word, array for multiple words
360
     * @return ResponseAdapter
361
     * @throws InvalidArgumentException If $stopWords is empty
362
     */
363 2
    public function addStopWords($stopWords): ResponseAdapter
364
    {
365 2
        $this->initializeStopWordsUrl();
366 2
        $json = $this->stopWordParser->toJson($stopWords);
367 2
        return $this->_sendRawPost($this->_stopWordsUrl, $json, 'application/json');
368
    }
369
370
    /**
371
     * Deletes a words from the managed stop word list
372
     *
373
     * @param string $stopWord stop word to delete
374
     * @return ResponseAdapter
375
     * @throws InvalidArgumentException If $stopWords is empty
376
     */
377 2
    public function deleteStopWord(string $stopWord): ResponseAdapter
378
    {
379 2
        $this->initializeStopWordsUrl();
380 2
        if (empty($stopWord)) {
381
            throw new InvalidArgumentException('Must provide stop word.');
382
        }
383
384 2
        return $this->_sendRawDelete($this->_stopWordsUrl . '/' . rawurlencode(rawurlencode($stopWord)));
385
    }
386
387 7
    protected function initializeSynonymsUrl()
388
    {
389 7
        if (trim($this->_synonymsUrl ?? '') !== '') {
390 7
            return;
391
        }
392 7
        $this->_synonymsUrl = $this->_constructUrl(self::SYNONYMS_SERVLET) . $this->getSchema()->getManagedResourceId();
393
    }
394
395 3
    protected function initializeStopWordsUrl()
396
    {
397 3
        if (trim($this->_stopWordsUrl ?? '') !== '') {
398 2
            return;
399
        }
400
401 3
        $this->_stopWordsUrl = $this->_constructUrl(self::STOPWORDS_SERVLET) . $this->getSchema()->getManagedResourceId();
402
    }
403
}
404