InfoModuleController   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 26
eloc 119
c 0
b 0
f 0
dl 0
loc 279
ccs 0
cts 168
cp 0
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A initializeAction() 0 5 1
A getFields() 0 16 4
A collectConnectionInfos() 0 36 5
A getCoreMetrics() 0 10 1
B collectIndexFieldsInfo() 0 46 6
A indexAction() 0 11 2
A collectIndexInspectorInfo() 0 20 3
A initializeView() 0 8 1
A collectStatistics() 0 32 2
A documentsDetailsAction() 0 4 1
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Controller\Backend\Search;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2010-2017 dkd Internet Service GmbH <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Api;
28
use ApacheSolrForTypo3\Solr\ConnectionManager;
29
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsRepository;
30
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Repository;
31
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
32
use ApacheSolrForTypo3\Solr\System\Validator\Path;
33
use TYPO3\CMS\Backend\Template\ModuleTemplate;
34
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
35
use TYPO3\CMS\Core\Messaging\FlashMessage;
36
use TYPO3\CMS\Core\Registry;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
39
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
40
41
/**
42
 * Info Module
43
 */
44
class InfoModuleController extends AbstractModuleController
0 ignored issues
show
Bug introduced by
The type ApacheSolrForTypo3\Solr\...bstractModuleController was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
45
{
46
    /**
47
     * @var ConnectionManager
48
     */
49
    protected $solrConnectionManager;
50
51
    /**
52
     * @var Repository
53
     */
54
    protected $apacheSolrDocumentRepository;
55
56
    /**
57
     * Initializes the controller before invoking an action method.
58
     */
59
    protected function initializeAction()
60
    {
61
        parent::initializeAction();
62
        $this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
63
        $this->apacheSolrDocumentRepository = GeneralUtility::makeInstance(Repository::class);
64
65
    }
66
    /**
67
     * Set up the doc header properly here
68
     *
69
     * @param ViewInterface $view
70
     * @return void
71
     */
72
    protected function initializeView(ViewInterface $view)
73
    {
74
        parent::initializeView($view);
75
76
        /* @var ModuleTemplate $module */ // holds the state of chosen tab
77
        $module = GeneralUtility::makeInstance(ModuleTemplate::class);
78
        $coreOptimizationTabs = $module->getDynamicTabMenu([], 'coreOptimization');
79
        $this->view->assign('tabs', $coreOptimizationTabs);
80
    }
81
82
    /**
83
     * Index action, shows an overview of the state of the Solr index
84
     *
85
     * @return void
86
     */
87
    public function indexAction()
88
    {
89
        if ($this->selectedSite === null) {
90
            $this->view->assign('can_not_proceed', true);
91
            return;
92
        }
93
94
        $this->collectConnectionInfos();
95
        $this->collectStatistics();
96
        $this->collectIndexFieldsInfo();
97
        $this->collectIndexInspectorInfo();
98
    }
99
100
    /**
101
     * @param string $type
102
     * @param int $uid
103
     * @param int $pageId
104
     * @param int $languageUid
105
     * @return void
106
     */
107
    public function documentsDetailsAction(string $type, int $uid, int $pageId, int $languageUid)
108
    {
109
        $documents = $this->apacheSolrDocumentRepository->findByTypeAndPidAndUidAndLanguageId($type, $uid, $pageId, $languageUid);
110
        $this->view->assign('documents', $documents);
111
    }
112
113
    /**
114
     * Checks whether the configured Solr server can be reached and provides a
115
     * flash message according to the result of the check.
116
     *
117
     * @return void
118
     */
119
    protected function collectConnectionInfos()
120
    {
121
        $connectedHosts = [];
122
        $missingHosts = [];
123
        $invalidPaths = [];
124
125
        /* @var Path $path */
126
        $path = GeneralUtility::makeInstance(Path::class);
127
        $connections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
128
129
        if (empty($connections)) {
130
            $this->view->assign('can_not_proceed', true);
131
            return;
132
        }
133
134
        foreach ($connections as $connection) {
135
            $coreAdmin = $connection->getAdminService();
136
            $coreUrl = (string)$coreAdmin;
137
138
            if ($coreAdmin->ping()) {
139
                $connectedHosts[] = $coreUrl;
140
            } else {
141
                $missingHosts[] = $coreUrl;
142
            }
143
144
            if (!$path->isValidSolrPath($coreAdmin->getCorePath())) {
145
                $invalidPaths[] = $coreAdmin->getCorePath();
146
            }
147
        }
148
149
        $this->view->assignMultiple([
150
            'site' => $this->selectedSite,
151
            'apiKey' => Api::getApiKey(),
152
            'connectedHosts' => $connectedHosts,
153
            'missingHosts' => $missingHosts,
154
            'invalidPaths' => $invalidPaths
155
        ]);
156
    }
157
158
    /**
159
     * Index action, shows an overview of the state of the Solr index
160
     *
161
     * @return void
162
     */
163
    protected function collectStatistics()
164
    {
165
        // TODO make time frame user adjustable, for now it's last 30 days
166
167
        $siteRootPageId = $this->selectedSite->getRootPageId();
168
        /* @var StatisticsRepository $statisticsRepository */
169
        $statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);
170
171
        // @TODO: Do we want Typoscript constants to restrict the results?
172
        $this->view->assign(
173
            'top_search_phrases',
174
            $statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
175
        );
176
        $this->view->assign(
177
            'top_search_phrases_without_hits',
178
            $statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
179
        );
180
        $this->view->assign(
181
            'search_phrases_statistics',
182
            $statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
183
        );
184
185
        $labels = [];
186
        $data = [];
187
        $chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
188
        foreach ($chartData as $bucket) {
189
            $labels[] = strftime('%x', $bucket['timestamp']);
190
            $data[] = (int)$bucket['numQueries'];
191
        }
192
193
        $this->view->assign('queriesChartLabels', json_encode($labels));
194
        $this->view->assign('queriesChartData', json_encode($data));
195
    }
196
197
    /**
198
     * Gets Luke meta data for the currently selected core and provides a list
199
     * of that data.
200
     *
201
     * @return void
202
     */
203
    protected function collectIndexFieldsInfo()
204
    {
205
        $indexFieldsInfoByCorePaths = [];
206
207
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
208
        foreach ($solrCoreConnections as $solrCoreConnection) {
209
            $coreAdmin = $solrCoreConnection->getAdminService();
210
211
            $indexFieldsInfo = [
212
                'corePath' => $coreAdmin->getCorePath()
213
            ];
214
            if ($coreAdmin->ping()) {
215
                $lukeData = $coreAdmin->getLukeMetaData();
216
217
                /* @var Registry $registry */
218
                $registry = GeneralUtility::makeInstance(Registry::class);
219
                $limit = $registry->get('tx_solr', 'luke.limit', 20000);
220
                $limitNote = '';
221
222
                if (isset($lukeData->index->numDocs) && $lukeData->index->numDocs > $limit) {
223
                    $limitNote = '<em>Too many terms</em>';
224
                } elseif (isset($lukeData->index->numDocs)) {
225
                    $limitNote = 'Nothing indexed';
226
                    // below limit, so we can get more data
227
                    // Note: we use 2 since 1 fails on Ubuntu Hardy.
228
                    $lukeData = $coreAdmin->getLukeMetaData(2);
229
                }
230
231
                $fields = $this->getFields($lukeData, $limitNote);
232
                $coreMetrics = $this->getCoreMetrics($lukeData, $fields);
233
234
                $indexFieldsInfo['noError'] = 'OK';
235
                $indexFieldsInfo['fields'] = $fields;
236
                $indexFieldsInfo['coreMetrics'] = $coreMetrics;
237
            } else {
238
                $indexFieldsInfo['noError'] = null;
239
240
                $this->addFlashMessage(
241
                    '',
242
                    'Unable to contact Apache Solr server: ' . $this->selectedSite->getLabel() . ' ' . $coreAdmin->getCorePath(),
243
                    FlashMessage::ERROR
244
                );
245
            }
246
            $indexFieldsInfoByCorePaths[$coreAdmin->getCorePath()] = $indexFieldsInfo;
247
        }
248
        $this->view->assign('indexFieldsInfoByCorePaths', $indexFieldsInfoByCorePaths);
249
    }
250
251
    /**
252
     * Retrieves the information for the index inspector.
253
     *
254
     * @return void
255
     */
256
    protected function collectIndexInspectorInfo()
257
    {
258
        $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
259
        $documentsByCoreAndType = [];
260
        foreach ($solrCoreConnections as $languageId => $solrCoreConnection) {
261
            $coreAdmin = $solrCoreConnection->getAdminService();
262
            $documents = $this->apacheSolrDocumentRepository->findByPageIdAndByLanguageId($this->selectedPageUID, $languageId);
263
264
            $documentsByType = [];
265
            foreach ($documents as $document) {
266
                $documentsByType[$document['type']][] = $document;
267
            }
268
269
            $documentsByCoreAndType[$languageId]['core'] = $coreAdmin;
270
            $documentsByCoreAndType[$languageId]['documents'] = $documentsByType;
271
        }
272
273
        $this->view->assignMultiple([
274
            'pageId' => $this->selectedPageUID,
275
            'indexInspectorDocumentsByLanguageAndType' => $documentsByCoreAndType
276
        ]);
277
    }
278
279
    /**
280
     * Gets field metrics.
281
     *
282
     * @param ResponseAdapter $lukeData Luke index data
283
     * @param string $limitNote Note to display if there are too many documents in the index to show number of terms for a field
284
     *
285
     * @return array An array of field metrics
286
     */
287
    protected function getFields(ResponseAdapter $lukeData, $limitNote)
288
    {
289
        $rows = [];
290
291
        $fields = (array)$lukeData->fields;
292
        foreach ($fields as $name => $field) {
293
            $rows[$name] = [
294
                'name' => $name,
295
                'type' => $field->type,
296
                'docs' => isset($field->docs) ? $field->docs : 0,
297
                'terms' => isset($field->distinct) ? $field->distinct : $limitNote
298
            ];
299
        }
300
        ksort($rows);
301
302
        return $rows;
303
    }
304
305
    /**
306
     * Gets general core metrics.
307
     *
308
     * @param ResponseAdapter $lukeData Luke index data
309
     * @param array $fields Fields metrics
310
     *
311
     * @return array An array of core metrics
312
     */
313
    protected function getCoreMetrics(ResponseAdapter $lukeData, array $fields)
314
    {
315
        $coreMetrics = [
316
            'numberOfDocuments' => $lukeData->index->numDocs,
317
            'numberOfDeletedDocuments' => $lukeData->index->deletedDocs,
318
            'numberOfTerms' => $lukeData->index->numTerms,
319
            'numberOfFields' => count($fields)
320
        ];
321
322
        return $coreMetrics;
323
    }
324
}
325