Passed
Push — release-11.5.x ( 39fc07...8ccd81 )
by Markus
34:52 queued 29:33
created

SolrAdminService::getSolrconfigName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3
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 FILE_SERVLET = 'admin/file';
43
    const SCHEMA_SERVLET = 'schema';
44
    const SYNONYMS_SERVLET = 'schema/analysis/synonyms/';
45
    const STOPWORDS_SERVLET = 'schema/analysis/stopwords/';
46
47
    /**
48
     * @var array
49
     */
50
    protected array $lukeData = [];
51
52
    /**
53
     * @var ResponseAdapter|null
54
     */
55
    protected ?ResponseAdapter $systemData = null;
56
57
    /**
58
     * @var ResponseAdapter|null
59
     */
60
    protected ?ResponseAdapter $pluginsData = null;
61
62
    /**
63
     * @var string|null
64
     */
65
    protected ?string $solrconfigName = null;
66
67
    /**
68
     * @var SchemaParser
69
     */
70
    protected SchemaParser $schemaParser;
71
72
    /**
73
     * @var Schema|null
74
     */
75
    protected ?Schema $schema = null;
76
77
    /**
78
     * @var string
79
     */
80
    protected string $_synonymsUrl = '';
81
82
    /**
83
     * @var string
84
     */
85
    protected string $_stopWordsUrl = '';
86
87
    /**
88
     * @var SynonymParser
89
     */
90
    protected SynonymParser $synonymParser;
91
92
    /**
93
     * @var StopWordParser
94
     */
95
    protected StopWordParser $stopWordParser;
96
97
    /**
98
     * Constructor
99
     *
100
     * @param Client $client
101
     * @param TypoScriptConfiguration|null $typoScriptConfiguration
102
     * @param SolrLogManager|null $logManager
103
     * @param SynonymParser|null $synonymParser
104
     * @param StopWordParser|null $stopWordParser
105
     * @param SchemaParser|null $schemaParser
106
     */
107 30
    public function __construct(
108
        Client $client,
109
        TypoScriptConfiguration $typoScriptConfiguration = null,
110
        SolrLogManager $logManager = null,
111
        SynonymParser $synonymParser = null,
112
        StopWordParser $stopWordParser = null,
113
        SchemaParser $schemaParser = null
114
    ) {
115 30
        parent::__construct($client, $typoScriptConfiguration, $logManager);
116
117 30
        $this->synonymParser = $synonymParser ?? GeneralUtility::makeInstance(SynonymParser::class);
118 30
        $this->stopWordParser = $stopWordParser ?? GeneralUtility::makeInstance(StopWordParser::class);
119 30
        $this->schemaParser = $schemaParser ?? GeneralUtility::makeInstance(SchemaParser::class);
120
    }
121
122
    /**
123
     * Call the /admin/system servlet and retrieve system information about Solr
124
     *
125
     * @return ResponseAdapter
126
     */
127 7
    public function system(): ResponseAdapter
128
    {
129 7
        return $this->_sendRawGet($this->_constructUrl(self::SYSTEM_SERVLET, ['wt' => 'json']));
130
    }
131
132
    /**
133
     * Gets information about the plugins installed in Solr
134
     *
135
     * @return ResponseAdapter|null A nested array of plugin data.
136
     */
137 5
    public function getPluginsInformation(): ?ResponseAdapter
138
    {
139 5
        if (count($this->pluginsData ?? []) === 0) {
140 5
            $url = $this->_constructUrl(self::PLUGINS_SERVLET, ['wt' => 'json']);
141 5
            $pluginsInformation = $this->_sendRawGet($url);
142
143
            /**
144
             * access a random property to trigger response parsing
145
             * @noinspection PhpExpressionResultUnusedInspection
146
             */
147 5
            $pluginsInformation->responseHeader;
148 5
            $this->pluginsData = $pluginsInformation;
149
        }
150
151 5
        return $this->pluginsData;
152
    }
153
154
    /**
155
     * get field meta data for the index
156
     *
157
     * @param int $numberOfTerms Number of top terms to fetch for each field
158
     * @return stdClass
159
     */
160
    public function getFieldsMetaData(int $numberOfTerms = 0): stdClass
161
    {
162
        return $this->getLukeMetaData($numberOfTerms)->fields;
163
    }
164
165
    /**
166
     * Retrieves metadata about the index from the luke request handler
167
     *
168
     * @param int $numberOfTerms Number of top terms to fetch for each field
169
     * @return ResponseAdapter Index meta data
170
     */
171 1
    public function getLukeMetaData(int $numberOfTerms = 0): ResponseAdapter
172
    {
173 1
        if (!isset($this->lukeData[$numberOfTerms])) {
174 1
            $lukeUrl = $this->_constructUrl(
175 1
                self::LUKE_SERVLET,
176 1
                ['numTerms' => $numberOfTerms, 'wt' => 'json', 'fl' => '*']
177 1
            );
178
179 1
            $this->lukeData[$numberOfTerms] = $this->_sendRawGet($lukeUrl);
180
        }
181
182 1
        return $this->lukeData[$numberOfTerms];
183
    }
184
185
    /**
186
     * Gets information about the Solr server
187
     *
188
     * @return ResponseAdapter
189
     */
190 7
    public function getSystemInformation(): ResponseAdapter
191
    {
192 7
        if (empty($this->systemData)) {
193 7
            $systemInformation = $this->system();
194
195
            /**
196
             * access a random property to trigger response parsing
197
             * @noinspection PhpExpressionResultUnusedInspection
198
             */
199 7
            $systemInformation->responseHeader;
200 7
            $this->systemData = $systemInformation;
201
        }
202
203 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...
204
    }
205
206
    /**
207
     * Gets the name of the solrconfig.xml file installed and in use on the Solr
208
     * server.
209
     *
210
     * @return string Name of the active solrconfig.xml
211
     */
212 4
    public function getSolrconfigName(): ?string
213
    {
214 4
        if (is_null($this->solrconfigName)) {
215 4
            $solrconfigXmlUrl = $this->_constructUrl(self::FILE_SERVLET, ['file' => 'solrconfig.xml']);
216 4
            $response = $this->_sendRawGet($solrconfigXmlUrl);
217 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

217
            $solrconfigXml = simplexml_load_string(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
218 4
            if ($solrconfigXml === false) {
219 1
                throw new InvalidArgumentException('No valid xml response from schema file: ' . $solrconfigXmlUrl);
220
            }
221 3
            $this->solrconfigName = (string)$solrconfigXml->attributes()->name;
222
        }
223
224 3
        return $this->solrconfigName;
225
    }
226
227
    /**
228
     * Gets the Solr server's version number.
229
     *
230
     * @return string Solr version number
231
     */
232 5
    public function getSolrServerVersion(): string
233
    {
234 5
        $systemInformation = $this->getSystemInformation();
235
        // don't know why $systemInformation->lucene->solr-spec-version won't work
236 5
        $luceneInformation = (array)$systemInformation->lucene;
237 5
        return $luceneInformation['solr-spec-version'] ?? '';
238
    }
239
240
    /**
241
     * Reloads the current core
242
     *
243
     * @return ResponseAdapter
244
     */
245 11
    public function reloadCore(): ResponseAdapter
246
    {
247 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

247
        return $this->reloadCoreByName(/** @scrutinizer ignore-type */ $this->getPrimaryEndpoint()->getCore());
Loading history...
248
    }
249
250
    /**
251
     * Reloads a core of the connection by a given core-name.
252
     *
253
     * @param string $coreName
254
     * @return ResponseAdapter
255
     */
256 11
    public function reloadCoreByName(string $coreName): ResponseAdapter
257
    {
258 11
        $coreAdminReloadUrl = $this->_constructUrl(self::CORES_SERVLET) . '?action=reload&core=' . $coreName;
259 11
        return $this->_sendRawGet($coreAdminReloadUrl);
260
    }
261
262
    /**
263
     * Get the configured schema for the current core.
264
     *
265
     * @return Schema
266
     */
267 14
    public function getSchema(): Schema
268
    {
269 14
        if ($this->schema !== null) {
270 3
            return $this->schema;
271
        }
272 14
        $response = $this->_sendRawGet($this->_constructUrl(self::SCHEMA_SERVLET));
273
274 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

274
        $this->schema = $this->schemaParser->parseJson(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
275 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...
276
    }
277
278
    /**
279
     * Get currently configured synonyms
280
     *
281
     * @param string $baseWord If given a base word, retrieves the synonyms for that word only
282
     * @return array
283
     */
284 7
    public function getSynonyms(string $baseWord = ''): array
285
    {
286 7
        $this->initializeSynonymsUrl();
287 7
        $synonymsUrl = $this->_synonymsUrl;
288 7
        if (!empty($baseWord)) {
289 7
            $synonymsUrl .= '/' . rawurlencode(rawurlencode($baseWord));
290
        }
291
292 7
        $response = $this->_sendRawGet($synonymsUrl);
293 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

293
        return $this->synonymParser->parseJson($baseWord, /** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
294
    }
295
296
    /**
297
     * Add list of synonyms for base word to managed synonyms map
298
     *
299
     * @param string $baseWord
300
     * @param array $synonyms
301
     *
302
     * @return ResponseAdapter
303
     */
304 7
    public function addSynonym(string $baseWord, array $synonyms): ResponseAdapter
305
    {
306 7
        $this->initializeSynonymsUrl();
307 7
        $json = $this->synonymParser->toJson($baseWord, $synonyms);
308 7
        return $this->_sendRawPost($this->_synonymsUrl, $json, 'application/json');
309
    }
310
311
    /**
312
     * Remove a synonym from the synonyms map
313
     *
314
     * @param string $baseWord
315
     * @return ResponseAdapter
316
     */
317 7
    public function deleteSynonym(string $baseWord): ResponseAdapter
318
    {
319 7
        $this->initializeSynonymsUrl();
320 7
        return $this->_sendRawDelete($this->_synonymsUrl . '/' . rawurlencode(rawurlencode($baseWord)));
321
    }
322
323
    /**
324
     * Get currently configured stop words
325
     *
326
     * @return array
327
     */
328 3
    public function getStopWords(): array
329
    {
330 3
        $this->initializeStopWordsUrl();
331 3
        $response = $this->_sendRawGet($this->_stopWordsUrl);
332 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

332
        return $this->stopWordParser->parseJson(/** @scrutinizer ignore-type */ $response->getRawResponse());
Loading history...
333
    }
334
335
    /**
336
     * Adds stop words to the managed stop word list
337
     *
338
     * @param array|string $stopWords string for a single word, array for multiple words
339
     * @return ResponseAdapter
340
     * @throws InvalidArgumentException If $stopWords is empty
341
     */
342 2
    public function addStopWords($stopWords): ResponseAdapter
343
    {
344 2
        $this->initializeStopWordsUrl();
345 2
        $json = $this->stopWordParser->toJson($stopWords);
346 2
        return $this->_sendRawPost($this->_stopWordsUrl, $json, 'application/json');
347
    }
348
349
    /**
350
     * Deletes a words from the managed stop word list
351
     *
352
     * @param string $stopWord stop word to delete
353
     * @return ResponseAdapter
354
     * @throws InvalidArgumentException If $stopWords is empty
355
     */
356 2
    public function deleteStopWord(string $stopWord): ResponseAdapter
357
    {
358 2
        $this->initializeStopWordsUrl();
359 2
        if (empty($stopWord)) {
360
            throw new InvalidArgumentException('Must provide stop word.');
361
        }
362
363 2
        return $this->_sendRawDelete($this->_stopWordsUrl . '/' . rawurlencode(rawurlencode($stopWord)));
364
    }
365
366 7
    protected function initializeSynonymsUrl()
367
    {
368 7
        if (trim($this->_synonymsUrl ?? '') !== '') {
369 7
            return;
370
        }
371 7
        $this->_synonymsUrl = $this->_constructUrl(self::SYNONYMS_SERVLET) . $this->getSchema()->getManagedResourceId();
372
    }
373
374 3
    protected function initializeStopWordsUrl()
375
    {
376 3
        if (trim($this->_stopWordsUrl ?? '') !== '') {
377 2
            return;
378
        }
379
380 3
        $this->_stopWordsUrl = $this->_constructUrl(self::STOPWORDS_SERVLET) . $this->getSchema()->getManagedResourceId();
381
    }
382
}
383