Passed
Push — master ( d5a28b...d12ca0 )
by
unknown
17:01
created

DatabaseIntegrityController::func_relations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
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 TYPO3\CMS\Lowlevel\Controller;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
22
use TYPO3\CMS\Backend\Template\ModuleTemplate;
23
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
24
use TYPO3\CMS\Backend\Utility\BackendUtility;
25
use TYPO3\CMS\Core\Database\ReferenceIndex;
26
use TYPO3\CMS\Core\Http\HtmlResponse;
27
use TYPO3\CMS\Core\Imaging\Icon;
28
use TYPO3\CMS\Core\Imaging\IconFactory;
29
use TYPO3\CMS\Core\Localization\LanguageService;
30
use TYPO3\CMS\Core\Messaging\FlashMessage;
31
use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
32
use TYPO3\CMS\Core\Page\PageRenderer;
33
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use TYPO3\CMS\Core\Utility\PathUtility;
36
use TYPO3\CMS\Fluid\View\StandaloneView;
37
use TYPO3\CMS\Lowlevel\Database\QueryGenerator;
38
use TYPO3\CMS\Lowlevel\Integrity\DatabaseIntegrityCheck;
39
40
/**
41
 * Script class for the DB int module
42
 * @internal This class is a specific Backend controller implementation and is not part of the TYPO3's Core API.
43
 */
44
class DatabaseIntegrityController
45
{
46
    /**
47
     * @var string
48
     */
49
    protected $formName = 'queryform';
50
51
    /**
52
     * The name of the module
53
     *
54
     * @var string
55
     */
56
    protected $moduleName = 'system_dbint';
57
58
    /**
59
     * @var StandaloneView
60
     */
61
    protected $view;
62
63
    /**
64
     * @var string
65
     */
66
    protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
67
68
    /**
69
     * ModuleTemplate Container
70
     *
71
     * @var ModuleTemplate
72
     */
73
    protected $moduleTemplate;
74
75
    /**
76
     * The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
77
     *
78
     * @see init()
79
     * @var array
80
     */
81
    protected $MOD_MENU = [
82
        'function' => []
83
    ];
84
85
    /**
86
     * Current settings for the keys of the MOD_MENU array
87
     *
88
     * @var array
89
     */
90
    protected $MOD_SETTINGS = [];
91
92
    protected IconFactory $iconFactory;
93
    protected PageRenderer $pageRenderer;
94
    protected UriBuilder $uriBuilder;
95
    protected ModuleTemplateFactory $moduleTemplateFactory;
96
97
    public function __construct(
98
        IconFactory $iconFactory,
99
        PageRenderer $pageRenderer,
100
        UriBuilder $uriBuilder,
101
        ModuleTemplateFactory $moduleTemplateFactory
102
    ) {
103
        $this->iconFactory = $iconFactory;
104
        $this->pageRenderer = $pageRenderer;
105
        $this->uriBuilder = $uriBuilder;
106
        $this->moduleTemplateFactory = $moduleTemplateFactory;
107
    }
108
109
    /**
110
     * Injects the request object for the current request or subrequest
111
     * Simply calls main() and init() and outputs the content
112
     *
113
     * @param ServerRequestInterface $request the current request
114
     * @return ResponseInterface the response with the content
115
     */
116
    public function mainAction(ServerRequestInterface $request): ResponseInterface
117
    {
118
        $this->getLanguageService()->includeLLFile('EXT:lowlevel/Resources/Private/Language/locallang.xlf');
119
        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
120
        $this->view->getRequest()->setControllerExtensionName('lowlevel');
121
122
        $this->menuConfig();
123
        $this->moduleTemplate = $this->moduleTemplateFactory->create($request);
124
125
        switch ($this->MOD_SETTINGS['function']) {
126
            case 'search':
127
                $templateFilename = 'CustomSearch.html';
128
                $this->func_search();
129
                break;
130
            case 'records':
131
                $templateFilename = 'RecordStatistics.html';
132
                $this->func_records();
133
                break;
134
            case 'relations':
135
                $templateFilename = 'Relations.html';
136
                $this->func_relations();
137
                break;
138
            case 'refindex':
139
                $templateFilename = 'ReferenceIndex.html';
140
                $this->func_refindex();
141
                break;
142
            default:
143
                $templateFilename = 'IntegrityOverview.html';
144
                $this->func_default();
145
        }
146
        $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . $templateFilename));
147
        $content = '<form action="" method="post" id="DatabaseIntegrityView" name="' . $this->formName . '">';
148
        $content .= $this->view->render();
149
        $content .= '</form>';
150
151
        // Setting up the shortcut button for docheader
152
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
153
        // Shortcut
154
        $shortCutButton = $buttonBar->makeShortcutButton()
155
            ->setRouteIdentifier($this->moduleName)
156
            ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
157
            ->setArguments([
158
                'SET' => [
159
                    'function' => $this->MOD_SETTINGS['function'] ?? '',
160
                    'search' => $this->MOD_SETTINGS['search'] ?? 'raw',
161
                    'search_query_makeQuery' => $this->MOD_SETTINGS['search_query_makeQuery'] ?? '',
162
                ]
163
            ]);
164
        $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
165
166
        $this->getModuleMenu();
167
168
        $this->moduleTemplate->setContent($content);
169
        return new HtmlResponse($this->moduleTemplate->renderContent());
170
    }
171
172
    /**
173
     * Configure menu
174
     */
175
    protected function menuConfig()
176
    {
177
        $lang = $this->getLanguageService();
178
        // MENU-ITEMS:
179
        // If array, then it's a selector box menu
180
        // If empty string it's just a variable, that'll be saved.
181
        // Values NOT in this array will not be saved in the settings-array for the module.
182
        $this->MOD_MENU = [
183
            'function' => [
184
                0 => htmlspecialchars($lang->getLL('menuTitle')),
185
                'records' => htmlspecialchars($lang->getLL('recordStatistics')),
186
                'relations' => htmlspecialchars($lang->getLL('databaseRelations')),
187
                'search' => htmlspecialchars($lang->getLL('fullSearch')),
188
                'refindex' => htmlspecialchars($lang->getLL('manageRefIndex'))
189
            ],
190
            'search' => [
191
                'raw' => htmlspecialchars($lang->getLL('rawSearch')),
192
                'query' => htmlspecialchars($lang->getLL('advancedQuery'))
193
            ],
194
            'search_query_smallparts' => '',
195
            'search_result_labels' => '',
196
            'labels_noprefix' => '',
197
            'options_sortlabel' => '',
198
            'show_deleted' => '',
199
            'queryConfig' => '',
200
            // Current query
201
            'queryTable' => '',
202
            // Current table
203
            'queryFields' => '',
204
            // Current tableFields
205
            'queryLimit' => '',
206
            // Current limit
207
            'queryOrder' => '',
208
            // Current Order field
209
            'queryOrderDesc' => '',
210
            // Current Order field descending flag
211
            'queryOrder2' => '',
212
            // Current Order2 field
213
            'queryOrder2Desc' => '',
214
            // Current Order2 field descending flag
215
            'queryGroup' => '',
216
            // Current Group field
217
            'storeArray' => '',
218
            // Used to store the available Query config memory banks
219
            'storeQueryConfigs' => '',
220
            // Used to store the available Query configs in memory
221
            'search_query_makeQuery' => [
222
                'all' => htmlspecialchars($lang->getLL('selectRecords')),
223
                'count' => htmlspecialchars($lang->getLL('countResults')),
224
                'explain' => htmlspecialchars($lang->getLL('explainQuery')),
225
                'csv' => htmlspecialchars($lang->getLL('csvExport'))
226
            ],
227
            'sword' => ''
228
        ];
229
        // CLEAN SETTINGS
230
        $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, [], $this->moduleName, 'ses');
231
        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
232
        if (GeneralUtility::_GP('queryConfig')) {
233
            $qA = GeneralUtility::_GP('queryConfig');
234
            $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize($qA)], $this->moduleName, 'ses');
235
        }
236
        $addConditionCheck = GeneralUtility::_GP('qG_ins');
237
        $setLimitToStart = false;
238
        foreach ($OLD_MOD_SETTINGS as $key => $val) {
239
            if (strpos($key, 'query') === 0 && $this->MOD_SETTINGS[$key] != $val && $key !== 'queryLimit' && $key !== 'use_listview') {
240
                $setLimitToStart = true;
241
                if ($key === 'queryTable' && !$addConditionCheck) {
242
                    $this->MOD_SETTINGS['queryConfig'] = '';
243
                }
244
            }
245
            if ($key === 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
246
                $this->MOD_SETTINGS['queryFields'] = '';
247
            }
248
        }
249
        if ($setLimitToStart) {
250
            $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
251
            if ($currentLimit[1]) {
252
                $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
253
            } else {
254
                $this->MOD_SETTINGS['queryLimit'] = '0';
255
            }
256
            $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
257
        }
258
    }
259
260
    /**
261
     * Generates the action menu
262
     */
263
    protected function getModuleMenu()
264
    {
265
        $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
266
        $menu->setIdentifier('DatabaseJumpMenu');
267
        foreach ($this->MOD_MENU['function'] as $controller => $title) {
268
            $item = $menu
269
                ->makeMenuItem()
270
                ->setHref(
271
                    (string)$this->uriBuilder->buildUriFromRoute(
272
                        $this->moduleName,
273
                        [
274
                            'id' => 0,
275
                            'SET' => [
276
                                'function' => $controller
277
                            ]
278
                        ]
279
                    )
280
                )
281
                ->setTitle($title);
282
            if ($controller === $this->MOD_SETTINGS['function']) {
283
                $item->setActive(true);
284
            }
285
            $menu->addMenuItem($item);
286
        }
287
        $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
288
    }
289
290
    /**
291
     * Creates the overview menu.
292
     */
293
    protected function func_default()
294
    {
295
        $modules = [];
296
        $availableModFuncs = ['records', 'relations', 'search', 'refindex'];
297
        foreach ($availableModFuncs as $modFunc) {
298
            $modules[$modFunc] = (string)$this->uriBuilder->buildUriFromRoute('system_dbint') . '&SET[function]=' . $modFunc;
299
        }
300
        $this->view->assign('availableFunctions', $modules);
301
    }
302
303
    /****************************
304
     *
305
     * Functionality implementation
306
     *
307
     ****************************/
308
    /**
309
     * Check and update reference index!
310
     */
311
    protected function func_refindex()
312
    {
313
        $readmeLocation = ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
314
        $this->view->assign('ReadmeLink', PathUtility::getAbsoluteWebPath($readmeLocation));
315
        $this->view->assign('ReadmeLocation', $readmeLocation);
316
        $this->view->assign('binaryPath', ExtensionManagementUtility::extPath('core', 'bin/typo3'));
317
318
        if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
319
            $testOnly = (bool)GeneralUtility::_GP('_check');
320
            $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
321
            $result = $refIndexObj->updateIndex($testOnly);
322
            $recordsCheckedString = $result['resultText'];
323
            $errors = $result['errors'];
324
            $flashMessage = GeneralUtility::makeInstance(
325
                FlashMessage::class,
326
                !empty($errors) ? implode("\n", $errors) : 'Index Integrity was perfect!',
327
                $recordsCheckedString,
328
                !empty($errors) ? FlashMessage::ERROR : FlashMessage::OK
329
            );
330
331
            $flashMessageRenderer = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve();
332
            $bodyContent = $flashMessageRenderer->render([$flashMessage]);
333
334
            $this->view->assign('content', nl2br($bodyContent));
335
        }
336
    }
337
338
    /**
339
     * Search (Full / Advanced)
340
     */
341
    protected function func_search()
342
    {
343
        $lang = $this->getLanguageService();
344
        $searchMode = $this->MOD_SETTINGS['search'];
345
        $fullsearch = GeneralUtility::makeInstance(QueryGenerator::class, $this->MOD_SETTINGS, $this->MOD_MENU, $this->moduleName);
346
        $fullsearch->setFormName($this->formName);
347
        $submenu = '<div class="form-inline form-inline-spaced">';
348
        $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
349
        if ($this->MOD_SETTINGS['search'] === 'query') {
350
            $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
351
        }
352
        $submenu .= '</div>';
353
        if ($this->MOD_SETTINGS['search'] === 'query') {
354
            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_query_smallparts]', $this->MOD_SETTINGS['search_query_smallparts'], '', '', 'id="checkSearch_query_smallparts"') . '<label class="form-check-label" for="checkSearch_query_smallparts">' . $lang->getLL('showSQL') . '</label></div>';
355
            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_result_labels]', $this->MOD_SETTINGS['search_result_labels'], '', '', 'id="checkSearch_result_labels"') . '<label class="form-check-label" for="checkSearch_result_labels">' . $lang->getLL('useFormattedStrings') . '</label></div>';
356
            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[labels_noprefix]', $this->MOD_SETTINGS['labels_noprefix'], '', '', 'id="checkLabels_noprefix"') . '<label class="form-check-label" for="checkLabels_noprefix">' . $lang->getLL('dontUseOrigValues') . '</label></div>';
357
            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[options_sortlabel]', $this->MOD_SETTINGS['options_sortlabel'], '', '', 'id="checkOptions_sortlabel"') . '<label class="form-check-label" for="checkOptions_sortlabel">' . $lang->getLL('sortOptions') . '</label></div>';
358
            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[show_deleted]', $this->MOD_SETTINGS['show_deleted'], '', '', 'id="checkShow_deleted"') . '<label class="form-check-label" for="checkShow_deleted">' . $lang->getLL('showDeleted') . '</label></div>';
359
        }
360
        $this->view->assign('submenu', $submenu);
361
        $this->view->assign('searchMode', $searchMode);
362
        switch ($searchMode) {
363
            case 'query':
364
                $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Lowlevel/QueryGenerator');
365
                $this->view->assign('queryMaker', $fullsearch->queryMaker());
366
                break;
367
            case 'raw':
368
            default:
369
                $this->view->assign('searchOptions', $fullsearch->form());
370
                $this->view->assign('results', $fullsearch->search());
371
        }
372
    }
373
374
    /**
375
     * Records overview
376
     */
377
    protected function func_records()
378
    {
379
        /** @var DatabaseIntegrityCheck $admin */
380
        $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
381
        $admin->genTree(0);
382
383
        // Pages stat
384
        $pageStatistic = [
385
            'total_pages' => [
386
                'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
387
                'count' => count($admin->getPageIdArray())
388
            ],
389
            'translated_pages' => [
390
                'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
391
                'count' => count($admin->getPageTranslatedPageIDArray()),
392
            ],
393
            'hidden_pages' => [
394
                'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
395
                'count' => $admin->getRecStats()['hidden'] ?? 0
396
            ],
397
            'deleted_pages' => [
398
                'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
399
                'count' => isset($admin->getRecStats()['deleted']['pages']) ? count($admin->getRecStats()['deleted']['pages']) : 0
400
            ]
401
        ];
402
403
        $lang = $this->getLanguageService();
404
405
        // Doktype
406
        $doktypes = [];
407
        $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
408
        if (is_array($doktype)) {
409
            foreach ($doktype as $setup) {
410
                if ($setup[1] !== '--div--') {
411
                    $doktypes[] = [
412
                        'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
413
                        'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
414
                        'count' => (int)($admin->getRecStats()['doktype'][$setup[1]] ?? 0)
415
                    ];
416
                }
417
            }
418
        }
419
420
        // Tables and lost records
421
        $id_list = '-1,0,' . implode(',', array_keys($admin->getPageIdArray()));
422
        $id_list = rtrim($id_list, ',');
423
        $admin->lostRecords($id_list);
424
        if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...T('fixLostRecords_uid') can also be of type string; however, parameter $uid of TYPO3\CMS\Lowlevel\Integ...yCheck::fixLostRecord() does only seem to accept integer, 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

424
        if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), /** @scrutinizer ignore-type */ GeneralUtility::_GET('fixLostRecords_uid'))) {
Loading history...
425
            $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
426
            $admin->genTree(0);
427
            $id_list = '-1,0,' . implode(',', array_keys($admin->getPageIdArray()));
428
            $id_list = rtrim($id_list, ',');
429
            $admin->lostRecords($id_list);
430
        }
431
        $tableStatistic = [];
432
        $countArr = $admin->countRecords($id_list);
433
        if (is_array($GLOBALS['TCA'])) {
434
            foreach ($GLOBALS['TCA'] as $t => $value) {
435
                if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
436
                    continue;
437
                }
438
                if ($t === 'pages' && $admin->getLostPagesList() !== '') {
439
                    $lostRecordCount = count(explode(',', $admin->getLostPagesList()));
440
                } else {
441
                    $lostRecordCount = isset($admin->getLRecords()[$t]) ? count($admin->getLRecords()[$t]) : 0;
442
                }
443
                if ($countArr['all'][$t]) {
444
                    $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
445
                } else {
446
                    $theNumberOfRe = '';
447
                }
448
                $lr = '';
449
                if (is_array($admin->getLRecords()[$t])) {
450
                    foreach ($admin->getLRecords()[$t] as $data) {
451
                        if (!GeneralUtility::inList($admin->getLostPagesList(), $data['pid'])) {
452
                            $lr .= '<div class="record"><a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('system_dbint') . '&SET[function]=records&fixLostRecords_table=' . $t . '&fixLostRecords_uid=' . $data['uid']) . '" title="' . htmlspecialchars($lang->getLL('fixLostRecord')) . '">' . $this->iconFactory->getIcon('status-dialog-error', Icon::SIZE_SMALL)->render() . '</a>uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
453
                        } else {
454
                            $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
455
                        }
456
                    }
457
                }
458
                $tableStatistic[$t] = [
459
                    'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
460
                    'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
461
                    'count' => $theNumberOfRe,
462
                    'lostRecords' => $lr
463
                ];
464
            }
465
        }
466
467
        $this->view->assignMultiple([
468
            'pages' => $pageStatistic,
469
            'doktypes' => $doktypes,
470
            'tables' => $tableStatistic
471
        ]);
472
    }
473
474
    /**
475
     * Show list references
476
     */
477
    protected function func_relations()
478
    {
479
        $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
480
        $admin->selectNonEmptyRecordsWithFkeys();
481
482
        $this->view->assignMultiple([
483
            'select_db' => $admin->testDBRefs($admin->getCheckSelectDBRefs()),
484
            'group_db' => $admin->testDBRefs($admin->getCheckGroupDBRefs())
485
        ]);
486
    }
487
488
    /**
489
     * Returns the Language Service
490
     * @return LanguageService
491
     */
492
    protected function getLanguageService()
493
    {
494
        return $GLOBALS['LANG'];
495
    }
496
}
497