Failed Conditions
Push — task/2976_TYPO3.11_compatibili... ( 14c9f4...2d3a36 )
by Rafael
23:17
created

InfoModuleController::collectIndexFieldsInfo()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 46
ccs 0
cts 26
cp 0
rs 8.8337
c 0
b 0
f 0
cc 6
nc 5
nop 0
crap 42
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Controller\Backend\Search;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2010-2017 dkd Internet Service GmbH <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\Api;
29
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsRepository;
30
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Repository as ApacheSolrDocumentRepository;
31
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
32
use ApacheSolrForTypo3\Solr\System\Validator\Path;
33
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
34
use Psr\Http\Message\ResponseInterface;
35
use Throwable;
36
use TYPO3\CMS\Backend\Template\ModuleTemplate;
37
use TYPO3\CMS\Core\Messaging\FlashMessage;
38
use TYPO3\CMS\Core\Registry;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3Fluid\Fluid\View\ViewInterface;
41
42
/**
43
 * Info Module
44
 */
45
class InfoModuleController extends AbstractModuleController
46
{
47
    /**
48
     * @var ApacheSolrDocumentRepository
49
     */
50
    protected ApacheSolrDocumentRepository $apacheSolrDocumentRepository;
51
52
    /**
53
     * Initializes the controller before invoking an action method.
54
     */
55
    protected function initializeAction()
56
    {
57
        parent::initializeAction();
58
        $this->apacheSolrDocumentRepository = GeneralUtility::makeInstance(ApacheSolrDocumentRepository::class);
59
    }
60
61
//    /**
62
//     * Set up the doc header properly here
63
//     *
64
//     * @param ViewInterface $view
65
//     * @return void
66
//     * @throws DBALDriverException
67
//     * @throws Throwable
68
//     */
69
//    protected function initializeView($view)
70
//    {
71
//        parent::initializeView($view);
72
//        $coreOptimizationTabs = $this->moduleTemplate->getDynamicTabMenu([], 'coreOptimization');
73
//        $this->view->assign('tabs', $coreOptimizationTabs);
74
//    }
75
76
    /**
77
     * Index action, shows an overview of the state of the Solr index
78
     *
79
     * @return void
80
     */
81
    public function indexAction(): ResponseInterface
82
    {
83
        if ($this->selectedSite === null) {
84
            $this->view->assign('can_not_proceed', true);
85
            return $this->htmlResponse(null);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->htmlResponse(null) returns the type Psr\Http\Message\ResponseInterface which is incompatible with the documented return type void.
Loading history...
86
        }
87
88
        $this->collectConnectionInfos();
89
        $this->collectStatistics();
90
        $this->collectIndexFieldsInfo();
91
        $this->collectIndexInspectorInfo();
92
        $this->moduleTemplate->setContent($this->view->render());
93
        return $this->htmlResponse($this->moduleTemplate->renderContent());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->htmlRespon...plate->renderContent()) returns the type Psr\Http\Message\ResponseInterface which is incompatible with the documented return type void.
Loading history...
94
    }
95
96
    /**
97
     * @param string $type
98
     * @param int $uid
99
     * @param int $pageId
100
     * @param int $languageUid
101
     * @return void
102
     */
103
    public function documentsDetailsAction(string $type, int $uid, int $pageId, int $languageUid): ResponseInterface
104
    {
105
        $documents = $this->apacheSolrDocumentRepository->findByTypeAndPidAndUidAndLanguageId($type, $uid, $pageId, $languageUid);
106
        $this->view->assign('documents', $documents);
107
        return $this->htmlResponse();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->htmlResponse() returns the type Psr\Http\Message\ResponseInterface which is incompatible with the documented return type void.
Loading history...
108
    }
109
110
    /**
111
     * Checks whether the configured Solr server can be reached and provides a
112
     * flash message according to the result of the check.
113
     *
114
     * @return void
115
     */
116
    protected function collectConnectionInfos()
117
    {
118
        $connectedHosts = [];
119
        $missingHosts = [];
120
        $invalidPaths = [];
121
122
        /* @var Path $path */
123
        $path = GeneralUtility::makeInstance(Path::class);
124
        $connections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
0 ignored issues
show
Bug introduced by
The method getConnectionsBySite() 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

124
        /** @scrutinizer ignore-call */ 
125
        $connections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);

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...
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

124
        $connections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
125
126
        if (empty($connections)) {
127
            $this->view->assign('can_not_proceed', true);
128
            return;
129
        }
130
131
        foreach ($connections as $connection) {
132
            $coreAdmin = $connection->getAdminService();
133
            $coreUrl = (string)$coreAdmin;
134
135
            if ($coreAdmin->ping()) {
136
                $connectedHosts[] = $coreUrl;
137
            } else {
138
                $missingHosts[] = $coreUrl;
139
            }
140
141
            if (!$path->isValidSolrPath($coreAdmin->getCorePath())) {
142
                $invalidPaths[] = $coreAdmin->getCorePath();
143
            }
144
        }
145
146
        $this->view->assignMultiple([
147
            'site' => $this->selectedSite,
148
            'apiKey' => Api::getApiKey(),
149
            'connectedHosts' => $connectedHosts,
150
            'missingHosts' => $missingHosts,
151
            'invalidPaths' => $invalidPaths
152
        ]);
153
    }
154
155
    /**
156
     * Index action, shows an overview of the state of the Solr index
157
     *
158
     * @return void
159
     */
160
    protected function collectStatistics()
161
    {
162
        // TODO make time frame user adjustable, for now it's last 30 days
163
164
        $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

164
        /** @scrutinizer ignore-call */ 
165
        $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...
165
        /* @var StatisticsRepository $statisticsRepository */
166
        $statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);
167
168
        // @TODO: Do we want Typoscript constants to restrict the results?
169
        $this->view->assign(
170
            'top_search_phrases',
171
            $statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
172
        );
173
        $this->view->assign(
174
            'top_search_phrases_without_hits',
175
            $statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
176
        );
177
        $this->view->assign(
178
            'search_phrases_statistics',
179
            $statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
180
        );
181
182
        $labels = [];
183
        $data = [];
184
        $chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
185
        foreach ($chartData as $bucket) {
186
            $labels[] = strftime('%x', $bucket['timestamp']);
187
            $data[] = (int)$bucket['numQueries'];
188
        }
189
190
        $this->view->assign('queriesChartLabels', json_encode($labels));
191
        $this->view->assign('queriesChartData', json_encode($data));
192
    }
193
194
    /**
195
     * Gets Luke metadata for the currently selected core and provides a list
196
     * of that data.
197
     *
198
     * @return void
199
     */
200
    protected function collectIndexFieldsInfo()
201
    {
202
        $indexFieldsInfoByCorePaths = [];
203
204
        $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

204
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
205
        foreach ($solrCoreConnections as $solrCoreConnection) {
206
            $coreAdmin = $solrCoreConnection->getAdminService();
207
208
            $indexFieldsInfo = [
209
                'corePath' => $coreAdmin->getCorePath()
210
            ];
211
            if ($coreAdmin->ping()) {
212
                $lukeData = $coreAdmin->getLukeMetaData();
213
214
                /* @var Registry $registry */
215
                $registry = GeneralUtility::makeInstance(Registry::class);
216
                $limit = $registry->get('tx_solr', 'luke.limit', 20000);
217
                $limitNote = '';
218
219
                if (isset($lukeData->index->numDocs) && $lukeData->index->numDocs > $limit) {
220
                    $limitNote = '<em>Too many terms</em>';
221
                } elseif (isset($lukeData->index->numDocs)) {
222
                    $limitNote = 'Nothing indexed';
223
                    // below limit, so we can get more data
224
                    // Note: we use 2 since 1 fails on Ubuntu Hardy.
225
                    $lukeData = $coreAdmin->getLukeMetaData(2);
226
                }
227
228
                $fields = $this->getFields($lukeData, $limitNote);
229
                $coreMetrics = $this->getCoreMetrics($lukeData, $fields);
230
231
                $indexFieldsInfo['noError'] = 'OK';
232
                $indexFieldsInfo['fields'] = $fields;
233
                $indexFieldsInfo['coreMetrics'] = $coreMetrics;
234
            } else {
235
                $indexFieldsInfo['noError'] = null;
236
237
                $this->addFlashMessage(
238
                    '',
239
                    'Unable to contact Apache Solr server: ' . $this->selectedSite->getLabel() . ' ' . $coreAdmin->getCorePath(),
240
                    FlashMessage::ERROR
241
                );
242
            }
243
            $indexFieldsInfoByCorePaths[$coreAdmin->getCorePath()] = $indexFieldsInfo;
244
        }
245
        $this->view->assign('indexFieldsInfoByCorePaths', $indexFieldsInfoByCorePaths);
246
    }
247
248
    /**
249
     * Retrieves the information for the index inspector.
250
     *
251
     * @return void
252
     */
253
    protected function collectIndexInspectorInfo()
254
    {
255
        $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

255
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite(/** @scrutinizer ignore-type */ $this->selectedSite);
Loading history...
256
        $documentsByCoreAndType = [];
257
        foreach ($solrCoreConnections as $languageId => $solrCoreConnection) {
258
            $coreAdmin = $solrCoreConnection->getAdminService();
259
            $documents = $this->apacheSolrDocumentRepository->findByPageIdAndByLanguageId($this->selectedPageUID, $languageId);
260
261
            $documentsByType = [];
262
            foreach ($documents as $document) {
263
                $documentsByType[$document['type']][] = $document;
264
            }
265
266
            $documentsByCoreAndType[$languageId]['core'] = $coreAdmin;
267
            $documentsByCoreAndType[$languageId]['documents'] = $documentsByType;
268
        }
269
270
        $this->view->assignMultiple([
271
            'pageId' => $this->selectedPageUID,
272
            'indexInspectorDocumentsByLanguageAndType' => $documentsByCoreAndType
273
        ]);
274
    }
275
276
    /**
277
     * Gets field metrics.
278
     *
279
     * @param ResponseAdapter $lukeData Luke index data
280
     * @param string $limitNote Note to display if there are too many documents in the index to show number of terms for a field
281
     *
282
     * @return array An array of field metrics
283
     */
284
    protected function getFields(ResponseAdapter $lukeData, string $limitNote): array
285
    {
286
        $rows = [];
287
288
        $fields = (array)$lukeData->fields;
289
        foreach ($fields as $name => $field) {
290
            $rows[$name] = [
291
                'name' => $name,
292
                'type' => $field->type,
293
                'docs' => $field->docs ?? 0,
294
                'terms' => $field->distinct ?? $limitNote
295
            ];
296
        }
297
        ksort($rows);
298
299
        return $rows;
300
    }
301
302
    /**
303
     * Gets general core metrics.
304
     *
305
     * @param ResponseAdapter $lukeData Luke index data
306
     * @param array $fields Fields metrics
307
     *
308
     * @return array An array of core metrics
309
     */
310
    protected function getCoreMetrics(ResponseAdapter $lukeData, array $fields): array
311
    {
312
        return [
313
            'numberOfDocuments' => $lukeData->index->numDocs,
314
            'numberOfDeletedDocuments' => $lukeData->index->deletedDocs,
315
            'numberOfTerms' => $lukeData->index->numTerms,
316
            'numberOfFields' => count($fields)
317
        ];
318
    }
319
}
320