Passed
Push — release-11.5.x ( a7f733...729324 )
by Rafael
31:39
created

InfoModuleController::collectConnectionInfos()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
c 1
b 0
f 0
dl 0
loc 41
ccs 0
cts 27
cp 0
rs 8.8657
cc 6
nc 7
nop 0
crap 42
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace ApacheSolrForTypo3\Solr\Controller\Backend\Search;
17
18
use ApacheSolrForTypo3\Solr\Api;
19
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Repository as ApacheSolrDocumentRepository;
20
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsRepository;
21
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
22
use ApacheSolrForTypo3\Solr\System\Validator\Path;
23
use Psr\Http\Message\ResponseInterface;
24
use TYPO3\CMS\Core\Messaging\FlashMessage;
25
use TYPO3\CMS\Core\Registry;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28
/**
29
 * Info Module
30
 */
31
class InfoModuleController extends AbstractModuleController
32
{
33
    /**
34
     * @var ApacheSolrDocumentRepository
35
     */
36
    protected ApacheSolrDocumentRepository $apacheSolrDocumentRepository;
37
38
    /**
39
     * @inheritDoc
40
     */
41
    protected function initializeAction()
42
    {
43
        parent::initializeAction();
44
        $this->apacheSolrDocumentRepository = GeneralUtility::makeInstance(ApacheSolrDocumentRepository::class);
45
    }
46
47
    /**
48
     * Index action, shows an overview of the state of the Solr index
49
     *
50
     * @return ResponseInterface
51
     */
52
    public function indexAction(): ResponseInterface
53
    {
54
        if ($this->selectedSite === null) {
55
            $this->view->assign('can_not_proceed', true);
56
            return $this->getModuleTemplateResponse();
57
        }
58
59
        $this->collectConnectionInfos();
60
        $this->collectStatistics();
61
        $this->collectIndexFieldsInfo();
62
        $this->collectIndexInspectorInfo();
63
64
        return $this->getModuleTemplateResponse();
65
    }
66
67
    /**
68
     * @param string $type
69
     * @param int $uid
70
     * @param int $pageId
71
     * @param int $languageUid
72
     * @return ResponseInterface
73
     */
74
    public function documentsDetailsAction(string $type, int $uid, int $pageId, int $languageUid): ResponseInterface
75
    {
76
        $documents = $this->apacheSolrDocumentRepository->findByTypeAndPidAndUidAndLanguageId($type, $uid, $pageId, $languageUid);
77
        $this->view->assign('documents', $documents);
78
        return $this->getModuleTemplateResponse();
79
    }
80
81
    /**
82
     * Checks whether the configured Solr server can be reached and provides a
83
     * flash message according to the result of the check.
84
     */
85
    protected function collectConnectionInfos(): void
86
    {
87
        $connectedHosts = [];
88
        $missingHosts = [];
89
        $invalidPaths = [];
90
91
        /* @var Path $path */
92
        $path = GeneralUtility::makeInstance(Path::class);
93
        $connections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
0 ignored issues
show
Bug introduced by
It seems like $this->selectedSite can also be of type null; however, parameter $site of ApacheSolrForTypo3\Solr\...:getConnectionsBySite() does only seem to accept ApacheSolrForTypo3\Solr\Domain\Site\Site, 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

93
        $connections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
94
95
        if (empty($connections)) {
96
            $this->view->assign('can_not_proceed', true);
97
            return;
98
        }
99
100
        $alreadyListedConnections = [];
101
        foreach ($connections as $connection) {
102
            $coreAdmin = $connection->getAdminService();
103
            $coreUrl = (string)$coreAdmin;
104
            if (in_array($coreUrl, $alreadyListedConnections)) {
105
                continue;
106
            }
107
            $alreadyListedConnections[] = $coreUrl;
108
109
            if ($coreAdmin->ping()) {
110
                $connectedHosts[] = $coreUrl;
111
            } else {
112
                $missingHosts[] = $coreUrl;
113
            }
114
115
            if (!$path->isValidSolrPath($coreAdmin->getCorePath())) {
116
                $invalidPaths[] = $coreAdmin->getCorePath();
117
            }
118
        }
119
120
        $this->view->assignMultiple([
121
            'site' => $this->selectedSite,
122
            'apiKey' => Api::getApiKey(),
123
            'connectedHosts' => $connectedHosts,
124
            'missingHosts' => $missingHosts,
125
            'invalidPaths' => $invalidPaths,
126
        ]);
127
    }
128
129
    /**
130
     * Index action, shows an overview of the state of the Solr index
131
     */
132
    protected function collectStatistics(): void
133
    {
134
        // TODO make time frame user adjustable, for now it's last 30 days
135
136
        $siteRootPageId = $this->selectedSite->getRootPageId();
0 ignored issues
show
Bug introduced by
The method getRootPageId() does not exist on null. ( Ignorable by Annotation )

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

136
        /** @scrutinizer ignore-call */ 
137
        $siteRootPageId = $this->selectedSite->getRootPageId();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
137
        /* @var StatisticsRepository $statisticsRepository */
138
        $statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);
139
140
        // @TODO: Do we want Typoscript constants to restrict the results?
141
        $this->view->assign(
142
            'top_search_phrases',
143
            $statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
144
        );
145
        $this->view->assign(
146
            'top_search_phrases_without_hits',
147
            $statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
148
        );
149
        $this->view->assign(
150
            'search_phrases_statistics',
151
            $statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
152
        );
153
154
        $labels = [];
155
        $data = [];
156
        $chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
157
        foreach ($chartData as $bucket) {
158
            // @todo Replace deprecated strftime in php 8.1. Suppress warning for now
159
            $labels[] = @strftime('%x', $bucket['timestamp']);
160
            $data[] = (int)$bucket['numQueries'];
161
        }
162
163
        $this->view->assign('queriesChartLabels', json_encode($labels));
164
        $this->view->assign('queriesChartData', json_encode($data));
165
    }
166
167
    /**
168
     * Gets Luke metadata for the currently selected core and provides a list
169
     * of that data.
170
     */
171
    protected function collectIndexFieldsInfo(): void
172
    {
173
        $indexFieldsInfoByCorePaths = [];
174
175
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
0 ignored issues
show
Bug introduced by
It seems like $this->selectedSite can also be of type null; however, parameter $site of ApacheSolrForTypo3\Solr\...:getConnectionsBySite() does only seem to accept ApacheSolrForTypo3\Solr\Domain\Site\Site, 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

175
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
176
        foreach ($solrCoreConnections as $solrCoreConnection) {
177
            $coreAdmin = $solrCoreConnection->getAdminService();
178
179
            $indexFieldsInfo = [
180
                'corePath' => $coreAdmin->getCorePath(),
181
            ];
182
            if ($coreAdmin->ping()) {
183
                $lukeData = $coreAdmin->getLukeMetaData();
184
185
                /* @var Registry $registry */
186
                $registry = GeneralUtility::makeInstance(Registry::class);
187
                $limit = $registry->get('tx_solr', 'luke.limit', 20000);
188
                $limitNote = '';
189
190
                if (isset($lukeData->index->numDocs) && $lukeData->index->numDocs > $limit) {
191
                    $limitNote = '<em>Too many terms</em>';
192
                } elseif (isset($lukeData->index->numDocs)) {
193
                    $limitNote = 'Nothing indexed';
194
                    // below limit, so we can get more data
195
                    // Note: we use 2 since 1 fails on Ubuntu Hardy.
196
                    $lukeData = $coreAdmin->getLukeMetaData(2);
197
                }
198
199
                $fields = $this->getFields($lukeData, $limitNote);
200
                $coreMetrics = $this->getCoreMetrics($lukeData, $fields);
201
202
                $indexFieldsInfo['noError'] = 'OK';
203
                $indexFieldsInfo['fields'] = $fields;
204
                $indexFieldsInfo['coreMetrics'] = $coreMetrics;
205
            } else {
206
                $indexFieldsInfo['noError'] = null;
207
208
                $this->addFlashMessage(
209
                    '',
210
                    'Unable to contact Apache Solr server: ' . $this->selectedSite->getLabel() . ' ' . $coreAdmin->getCorePath(),
211
                    FlashMessage::ERROR
212
                );
213
            }
214
            $indexFieldsInfoByCorePaths[$coreAdmin->getCorePath()] = $indexFieldsInfo;
215
        }
216
        $this->view->assign('indexFieldsInfoByCorePaths', $indexFieldsInfoByCorePaths);
217
    }
218
219
    /**
220
     * Retrieves the information for the index inspector.
221
     */
222
    protected function collectIndexInspectorInfo(): void
223
    {
224
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
0 ignored issues
show
Bug introduced by
It seems like $this->selectedSite can also be of type null; however, parameter $site of ApacheSolrForTypo3\Solr\...:getConnectionsBySite() does only seem to accept ApacheSolrForTypo3\Solr\Domain\Site\Site, 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

224
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
225
        $documentsByCoreAndType = [];
226
        $alreadyListedCores = [];
227
        foreach ($solrCoreConnections as $languageId => $solrCoreConnection) {
228
            $coreAdmin = $solrCoreConnection->getAdminService();
229
230
            // Do not list cores twice when multiple languages use the same core
231
            $url = (string)$coreAdmin;
232
            if (in_array($url, $alreadyListedCores)) {
233
                continue;
234
            }
235
            $alreadyListedCores[] = $url;
236
237
            $documents = $this->apacheSolrDocumentRepository->findByPageIdAndByLanguageId($this->selectedPageUID, $languageId);
238
239
            $documentsByType = [];
240
            foreach ($documents as $document) {
241
                $documentsByType[$document['type']][] = $document;
242
            }
243
244
            $documentsByCoreAndType[$languageId]['core'] = $coreAdmin;
245
            $documentsByCoreAndType[$languageId]['documents'] = $documentsByType;
246
        }
247
248
        $this->view->assignMultiple([
249
            'pageId' => $this->selectedPageUID,
250
            'indexInspectorDocumentsByLanguageAndType' => $documentsByCoreAndType,
251
        ]);
252
    }
253
254
    /**
255
     * Gets field metrics.
256
     *
257
     * @param ResponseAdapter $lukeData Luke index data
258
     * @param string $limitNote Note to display if there are too many documents in the index to show number of terms for a field
259
     *
260
     * @return array An array of field metrics
261
     */
262
    protected function getFields(ResponseAdapter $lukeData, string $limitNote): array
263
    {
264
        $rows = [];
265
266
        $fields = (array)$lukeData->fields;
267
        foreach ($fields as $name => $field) {
268
            $rows[$name] = [
269
                'name' => $name,
270
                'type' => $field->type,
271
                'docs' => $field->docs ?? 0,
272
                'terms' => $field->distinct ?? $limitNote,
273
            ];
274
        }
275
        ksort($rows);
276
277
        return $rows;
278
    }
279
280
    /**
281
     * Gets general core metrics.
282
     *
283
     * @param ResponseAdapter $lukeData Luke index data
284
     * @param array $fields Fields metrics
285
     *
286
     * @return array An array of core metrics
287
     */
288
    protected function getCoreMetrics(ResponseAdapter $lukeData, array $fields): array
289
    {
290
        return [
291
            'numberOfDocuments' => $lukeData->index->numDocs ?? 0,
292
            'numberOfDeletedDocuments' => $lukeData->index->deletedDocs ?? 0,
293
            'numberOfTerms' => $lukeData->index->numTerms ?? 0,
294
            'numberOfFields' => count($fields),
295
        ];
296
    }
297
}
298