33b778fb54116544ac87b5999439aefbbacf0abc
[Packages/TYPO3.CMS.git] / typo3 / sysext / lowlevel / Classes / Controller / DatabaseIntegrityController.php
1 <?php
2 namespace TYPO3\CMS\Lowlevel\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
20 use TYPO3\CMS\Backend\Template\ModuleTemplate;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Database\QueryView;
23 use TYPO3\CMS\Core\Database\ReferenceIndex;
24 use TYPO3\CMS\Core\Imaging\Icon;
25 use TYPO3\CMS\Core\Imaging\IconFactory;
26 use TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck;
27 use TYPO3\CMS\Core\Localization\LanguageService;
28 use TYPO3\CMS\Core\Page\PageRenderer;
29 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\PathUtility;
32 use TYPO3\CMS\Fluid\View\StandaloneView;
33
34 /**
35 * Script class for the DB int module
36 */
37 class DatabaseIntegrityController
38 {
39 /**
40 * @var string
41 */
42 protected $formName = 'queryform';
43
44 /**
45 * The name of the module
46 *
47 * @var string
48 */
49 protected $moduleName = 'system_dbint';
50
51 /**
52 * @var StandaloneView
53 */
54 protected $view;
55
56 /**
57 * @var string
58 */
59 protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
60
61 /**
62 * @var IconFactory
63 */
64 protected $iconFactory;
65
66 /**
67 * ModuleTemplate Container
68 *
69 * @var ModuleTemplate
70 */
71 protected $moduleTemplate;
72
73 /**
74 * Loaded with the global array $MCONF which holds some module configuration from the conf.php file of backend modules.
75 *
76 * @see init()
77 * @var array
78 */
79 protected $MCONF = [
80 'name' => 'system_dbint',
81 ];
82
83 /**
84 * The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
85 *
86 * @see init()
87 * @var array
88 */
89 protected $MOD_MENU = [
90 'function' => []
91 ];
92
93 /**
94 * Current settings for the keys of the MOD_MENU array
95 *
96 * @see $MOD_MENU
97 * @var array
98 */
99 protected $MOD_SETTINGS = [];
100
101 /**
102 * Injects the request object for the current request or subrequest
103 * Simply calls main() and init() and outputs the content
104 *
105 * @param ServerRequestInterface $request the current request
106 * @param ResponseInterface $response
107 * @return ResponseInterface the response with the content
108 */
109 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
110 {
111 $this->getLanguageService()->includeLLFile('EXT:lowlevel/Resources/Private/Language/locallang.xlf');
112 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
113 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
114 $this->view->getRequest()->setControllerExtensionName('lowlevel');
115
116 $this->menuConfig();
117 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
118 $this->moduleTemplate->addJavaScriptCode(
119 'jumpToUrl',
120 '
121 function jumpToUrl(URL) {
122 window.location.href = URL;
123 return false;
124 }
125 '
126 );
127
128 switch ($this->MOD_SETTINGS['function']) {
129 case 'search':
130 $templateFilename = 'CustomSearch.html';
131 $this->func_search();
132 break;
133 case 'records':
134 $templateFilename = 'RecordStatistics.html';
135 $this->func_records();
136 break;
137 case 'relations':
138 $templateFilename = 'Relations.html';
139 $this->func_relations();
140 break;
141 case 'refindex':
142 $templateFilename = 'ReferenceIndex.html';
143 $this->func_refindex();
144 break;
145 default:
146 $templateFilename = 'IntegrityOverview.html';
147 $this->func_default();
148 }
149 $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . $templateFilename));
150 $content = '<form action="" method="post" id="DatabaseIntegrityView" name="' . $this->formName . '">';
151 $content .= $this->view->render();
152 $content .= '</form>';
153
154 // Setting up the shortcut button for docheader
155 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
156 // Shortcut
157 $shortCutButton = $buttonBar->makeShortcutButton()
158 ->setModuleName($this->moduleName)
159 ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
160 ->setSetVariables(['function', 'search', 'search_query_makeQuery']);
161 $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
162
163 $this->getModuleMenu();
164
165 $this->moduleTemplate->setContent($content);
166 $response->getBody()->write($this->moduleTemplate->renderContent());
167 return $response;
168 }
169
170 /**
171 * Configure menu
172 */
173 protected function menuConfig()
174 {
175 $lang = $this->getLanguageService();
176 // MENU-ITEMS:
177 // If array, then it's a selector box menu
178 // If empty string it's just a variable, that'll be saved.
179 // Values NOT in this array will not be saved in the settings-array for the module.
180 $this->MOD_MENU = [
181 'function' => [
182 0 => htmlspecialchars($lang->getLL('menuTitle')),
183 'records' => htmlspecialchars($lang->getLL('recordStatistics')),
184 'relations' => htmlspecialchars($lang->getLL('databaseRelations')),
185 'search' => htmlspecialchars($lang->getLL('fullSearch')),
186 'refindex' => htmlspecialchars($lang->getLL('manageRefIndex'))
187 ],
188 'search' => [
189 'raw' => htmlspecialchars($lang->getLL('rawSearch')),
190 'query' => htmlspecialchars($lang->getLL('advancedQuery'))
191 ],
192 'search_query_smallparts' => '',
193 'search_result_labels' => '',
194 'labels_noprefix' => '',
195 'options_sortlabel' => '',
196 'show_deleted' => '',
197 'queryConfig' => '',
198 // Current query
199 'queryTable' => '',
200 // Current table
201 'queryFields' => '',
202 // Current tableFields
203 'queryLimit' => '',
204 // Current limit
205 'queryOrder' => '',
206 // Current Order field
207 'queryOrderDesc' => '',
208 // Current Order field descending flag
209 'queryOrder2' => '',
210 // Current Order2 field
211 'queryOrder2Desc' => '',
212 // Current Order2 field descending flag
213 'queryGroup' => '',
214 // Current Group field
215 'storeArray' => '',
216 // Used to store the available Query config memory banks
217 'storeQueryConfigs' => '',
218 // Used to store the available Query configs in memory
219 'search_query_makeQuery' => [
220 'all' => htmlspecialchars($lang->getLL('selectRecords')),
221 'count' => htmlspecialchars($lang->getLL('countResults')),
222 'explain' => htmlspecialchars($lang->getLL('explainQuery')),
223 'csv' => htmlspecialchars($lang->getLL('csvExport'))
224 ],
225 'sword' => ''
226 ];
227 // CLEAN SETTINGS
228 $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, '', $this->moduleName, 'ses');
229 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
230 if (GeneralUtility::_GP('queryConfig')) {
231 $qA = GeneralUtility::_GP('queryConfig');
232 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize($qA)], $this->moduleName, 'ses');
233 }
234 $addConditionCheck = GeneralUtility::_GP('qG_ins');
235 $setLimitToStart = false;
236 foreach ($OLD_MOD_SETTINGS as $key => $val) {
237 if (substr($key, 0, 5) === 'query' && $this->MOD_SETTINGS[$key] != $val && $key !== 'queryLimit' && $key !== 'use_listview') {
238 $setLimitToStart = true;
239 if ($key === 'queryTable' && !$addConditionCheck) {
240 $this->MOD_SETTINGS['queryConfig'] = '';
241 }
242 }
243 if ($key === 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
244 $this->MOD_SETTINGS['queryFields'] = '';
245 }
246 }
247 if ($setLimitToStart) {
248 $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
249 if ($currentLimit[1]) {
250 $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
251 } else {
252 $this->MOD_SETTINGS['queryLimit'] = '0';
253 }
254 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
255 }
256 }
257
258 /**
259 * Generates the action menu
260 */
261 protected function getModuleMenu()
262 {
263 $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
264 $menu->setIdentifier('DatabaseJumpMenu');
265 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
266 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
267 foreach ($this->MOD_MENU['function'] as $controller => $title) {
268 $item = $menu
269 ->makeMenuItem()
270 ->setHref(
271 (string)$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 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
298 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
299 foreach ($availableModFuncs as $modFunc) {
300 $modules[$modFunc] = (string)$uriBuilder->buildUriFromRoute('system_dbint') . '&SET[function]=' . $modFunc;
301 }
302 $this->view->assign('availableFunctions', $modules);
303 }
304
305 /****************************
306 *
307 * Functionality implementation
308 *
309 ****************************/
310 /**
311 * Check and update reference index!
312 */
313 protected function func_refindex()
314 {
315 $readmeLocation = ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
316 $this->view->assign('ReadmeLink', PathUtility::getAbsoluteWebPath($readmeLocation));
317 $this->view->assign('ReadmeLocation', $readmeLocation);
318 $this->view->assign('binaryPath', ExtensionManagementUtility::extPath('core', 'bin/typo3'));
319
320 if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
321 $testOnly = (bool)GeneralUtility::_GP('_check');
322 // Call the functionality
323 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
324 $refIndexObj->enableRuntimeCache();
325 list(, $bodyContent) = $refIndexObj->updateIndex($testOnly);
326 $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
327 }
328 }
329
330 /**
331 * Search (Full / Advanced)
332 */
333 protected function func_search()
334 {
335 $lang = $this->getLanguageService();
336 $searchMode = $this->MOD_SETTINGS['search'];
337 $fullsearch = GeneralUtility::makeInstance(QueryView::class, $this->MOD_SETTINGS, $this->MOD_MENU, $this->moduleName);
338 $fullsearch->setFormName($this->formName);
339 $submenu = '<div class="form-inline form-inline-spaced">';
340 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
341 if ($this->MOD_SETTINGS['search'] === 'query') {
342 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
343 }
344 $submenu .= '</div>';
345 if ($this->MOD_SETTINGS['search'] === 'query') {
346 $submenu .= '<div class="checkbox"><label for="checkSearch_query_smallparts">' . BackendUtility::getFuncCheck(0, 'SET[search_query_smallparts]', $this->MOD_SETTINGS['search_query_smallparts'], '', '', 'id="checkSearch_query_smallparts"') . $lang->getLL('showSQL') . '</label></div>';
347 $submenu .= '<div class="checkbox"><label for="checkSearch_result_labels">' . BackendUtility::getFuncCheck(0, 'SET[search_result_labels]', $this->MOD_SETTINGS['search_result_labels'], '', '', 'id="checkSearch_result_labels"') . $lang->getLL('useFormattedStrings') . '</label></div>';
348 $submenu .= '<div class="checkbox"><label for="checkLabels_noprefix">' . BackendUtility::getFuncCheck(0, 'SET[labels_noprefix]', $this->MOD_SETTINGS['labels_noprefix'], '', '', 'id="checkLabels_noprefix"') . $lang->getLL('dontUseOrigValues') . '</label></div>';
349 $submenu .= '<div class="checkbox"><label for="checkOptions_sortlabel">' . BackendUtility::getFuncCheck(0, 'SET[options_sortlabel]', $this->MOD_SETTINGS['options_sortlabel'], '', '', 'id="checkOptions_sortlabel"') . $lang->getLL('sortOptions') . '</label></div>';
350 $submenu .= '<div class="checkbox"><label for="checkShow_deleted">' . BackendUtility::getFuncCheck(0, 'SET[show_deleted]', $this->MOD_SETTINGS['show_deleted'], '', '', 'id="checkShow_deleted"') . $lang->getLL('showDeleted') . '</label></div>';
351 }
352 $this->view->assign('submenu', $submenu);
353 $this->view->assign('searchMode', $searchMode);
354 switch ($searchMode) {
355 case 'query':
356 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Lowlevel/QueryGenerator');
357 $this->view->assign('queryMaker', $fullsearch->queryMaker());
358 break;
359 case 'raw':
360 default:
361 $this->view->assign('searchOptions', $fullsearch->form());
362 $this->view->assign('results', $fullsearch->search());
363 }
364 }
365
366 /**
367 * Records overview
368 */
369 protected function func_records()
370 {
371 /** @var $admin DatabaseIntegrityCheck */
372 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
373 $admin->genTree(0);
374
375 // Pages stat
376 $pageStatistic = [
377 'total_pages' => [
378 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
379 'count' => count($admin->page_idArray)
380 ],
381 'translated_pages' => [
382 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
383 'count' => count($admin->getPageTranslatedPageIDArray()),
384 ],
385 'hidden_pages' => [
386 'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
387 'count' => $admin->recStats['hidden']
388 ],
389 'deleted_pages' => [
390 'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
391 'count' => isset($admin->recStats['deleted']['pages']) ? count($admin->recStats['deleted']['pages']) : 0
392 ]
393 ];
394
395 $lang = $this->getLanguageService();
396
397 // Doktype
398 $doktypes = [];
399 $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
400 if (is_array($doktype)) {
401 foreach ($doktype as $setup) {
402 if ($setup[1] !== '--div--') {
403 $doktypes[] = [
404 'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
405 'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
406 'count' => (int)$admin->recStats['doktype'][$setup[1]]
407 ];
408 }
409 }
410 }
411
412 // Tables and lost records
413 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
414 $id_list = rtrim($id_list, ',');
415 $admin->lostRecords($id_list);
416 if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
417 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
418 $admin->genTree(0);
419 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
420 $id_list = rtrim($id_list, ',');
421 $admin->lostRecords($id_list);
422 }
423 $tableStatistic = [];
424 $countArr = $admin->countRecords($id_list);
425 if (is_array($GLOBALS['TCA'])) {
426 foreach ($GLOBALS['TCA'] as $t => $value) {
427 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
428 continue;
429 }
430 if ($t === 'pages' && $admin->lostPagesList !== '') {
431 $lostRecordCount = count(explode(',', $admin->lostPagesList));
432 } else {
433 $lostRecordCount = isset($admin->lRecords[$t]) ? count($admin->lRecords[$t]) : 0;
434 }
435 if ($countArr['all'][$t]) {
436 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
437 } else {
438 $theNumberOfRe = '';
439 }
440 $lr = '';
441 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
442 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
443 if (is_array($admin->lRecords[$t])) {
444 foreach ($admin->lRecords[$t] as $data) {
445 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
446 $lr .= '<div class="record"><a href="' . htmlspecialchars(((string)$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>';
447 } else {
448 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
449 }
450 }
451 }
452 $tableStatistic[$t] = [
453 'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
454 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
455 'count' => $theNumberOfRe,
456 'lostRecords' => $lr
457 ];
458 }
459 }
460
461 $this->view->assignMultiple([
462 'pages' => $pageStatistic,
463 'doktypes' => $doktypes,
464 'tables' => $tableStatistic
465 ]);
466 }
467
468 /**
469 * Show list references
470 */
471 protected function func_relations()
472 {
473 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
474 $fkey_arrays = $admin->getGroupFields('');
475 $admin->selectNonEmptyRecordsWithFkeys($fkey_arrays);
476 $fileTest = $admin->testFileRefs();
477
478 if (is_array($fileTest['noFile'])) {
479 ksort($fileTest['noFile']);
480 }
481 $this->view->assignMultiple([
482 'files' => $fileTest,
483 'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
484 'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
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 /**
498 * @return PageRenderer
499 */
500 protected function getPageRenderer()
501 {
502 return GeneralUtility::makeInstance(PageRenderer::class);
503 }
504 }