Passed
Push — task/2976_TYPO3.11_compatibili... ( 9f9205...0f123d )
by Rafael
27:04
created

InfoModuleController::indexAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 13
ccs 0
cts 10
cp 0
rs 9.9666
cc 2
nc 2
nop 0
crap 6
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Controller\Backend\Search;
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
use ApacheSolrForTypo3\Solr\Api;
19
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsRepository;
20
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Repository as ApacheSolrDocumentRepository;
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->htmlResponse();
57
        }
58
59
        $this->collectConnectionInfos();
60
        $this->collectStatistics();
61
        $this->collectIndexFieldsInfo();
62
        $this->collectIndexInspectorInfo();
63
        $this->moduleTemplate->setContent($this->view->render());
64
        return $this->htmlResponse($this->moduleTemplate->renderContent());
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->htmlResponse();
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
     * @return void
86
     */
87
    protected function collectConnectionInfos(): void
88
    {
89
        $connectedHosts = [];
90
        $missingHosts = [];
91
        $invalidPaths = [];
92
93
        /* @var Path $path */
94
        $path = GeneralUtility::makeInstance(Path::class);
95
        $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

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

135
        /** @scrutinizer ignore-call */ 
136
        $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...
136
        /* @var StatisticsRepository $statisticsRepository */
137
        $statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);
138
139
        // @TODO: Do we want Typoscript constants to restrict the results?
140
        $this->view->assign(
141
            'top_search_phrases',
142
            $statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
143
        );
144
        $this->view->assign(
145
            'top_search_phrases_without_hits',
146
            $statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
147
        );
148
        $this->view->assign(
149
            'search_phrases_statistics',
150
            $statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
151
        );
152
153
        $labels = [];
154
        $data = [];
155
        $chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
156
        foreach ($chartData as $bucket) {
157
            $labels[] = strftime('%x', $bucket['timestamp']);
158
            $data[] = (int)$bucket['numQueries'];
159
        }
160
161
        $this->view->assign('queriesChartLabels', json_encode($labels));
162
        $this->view->assign('queriesChartData', json_encode($data));
163
    }
164
165
    /**
166
     * Gets Luke metadata for the currently selected core and provides a list
167
     * of that data.
168
     *
169
     * @return void
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
     * @return void
223
     */
224
    protected function collectIndexInspectorInfo(): void
225
    {
226
        $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

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