d8934a9911e4b656f7a2027ca47d642c60d9b7d5
[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
266 foreach ($this->MOD_MENU['function'] as $controller => $title) {
267 $item = $menu
268 ->makeMenuItem()
269 ->setHref(
270 BackendUtility::getModuleUrl(
271 $this->moduleName,
272 [
273 'id' => 0,
274 'SET' => [
275 'function' => $controller
276 ]
277 ]
278 )
279 )
280 ->setTitle($title);
281 if ($controller === $this->MOD_SETTINGS['function']) {
282 $item->setActive(true);
283 }
284 $menu->addMenuItem($item);
285 }
286 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
287 }
288
289 /**
290 * Creates the overview menu.
291 */
292 protected function func_default()
293 {
294 $modules = [];
295 $availableModFuncs = ['records', 'relations', 'search', 'refindex'];
296 foreach ($availableModFuncs as $modFunc) {
297 $modules[$modFunc] = BackendUtility::getModuleUrl('system_dbint') . '&SET[function]=' . $modFunc;
298 }
299 $this->view->assign('availableFunctions', $modules);
300 }
301
302 /****************************
303 *
304 * Functionality implementation
305 *
306 ****************************/
307 /**
308 * Check and update reference index!
309 */
310 protected function func_refindex()
311 {
312 $readmeLocation = ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
313 $this->view->assign('ReadmeLink', PathUtility::getAbsoluteWebPath($readmeLocation));
314 $this->view->assign('ReadmeLocation', $readmeLocation);
315 $this->view->assign('binaryPath', ExtensionManagementUtility::extPath('core', 'bin/typo3'));
316
317 if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
318 $testOnly = (bool)GeneralUtility::_GP('_check');
319 // Call the functionality
320 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
321 $refIndexObj->enableRuntimeCache();
322 list(, $bodyContent) = $refIndexObj->updateIndex($testOnly);
323 $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
324 }
325 }
326
327 /**
328 * Search (Full / Advanced)
329 */
330 protected function func_search()
331 {
332 $lang = $this->getLanguageService();
333 $searchMode = $this->MOD_SETTINGS['search'];
334 $fullsearch = GeneralUtility::makeInstance(QueryView::class);
335 $fullsearch->setFormName($this->formName);
336 $submenu = '<div class="form-inline form-inline-spaced">';
337 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
338 if ($this->MOD_SETTINGS['search'] === 'query') {
339 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
340 }
341 $submenu .= '</div>';
342 if ($this->MOD_SETTINGS['search'] === 'query') {
343 $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>';
344 $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>';
345 $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>';
346 $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>';
347 $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>';
348 }
349 $this->view->assign('submenu', $submenu);
350 $this->view->assign('searchMode', $searchMode);
351 switch ($searchMode) {
352 case 'query':
353 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Lowlevel/QueryGenerator');
354 $this->view->assign('queryMaker', $fullsearch->queryMaker());
355 break;
356 case 'raw':
357 default:
358 $this->view->assign('searchOptions', $fullsearch->form());
359 $this->view->assign('results', $fullsearch->search());
360 }
361 }
362
363 /**
364 * Records overview
365 */
366 protected function func_records()
367 {
368 /** @var $admin DatabaseIntegrityCheck */
369 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
370 $admin->genTree(0);
371
372 // Pages stat
373 $pageStatistic = [
374 'total_pages' => [
375 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
376 'count' => count($admin->page_idArray)
377 ],
378 'hidden_pages' => [
379 'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
380 'count' => $admin->recStats['hidden']
381 ],
382 'deleted_pages' => [
383 'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
384 'count' => count($admin->recStats['deleted']['pages'])
385 ]
386 ];
387
388 $lang = $this->getLanguageService();
389
390 // Doktype
391 $doktypes = [];
392 $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
393 if (is_array($doktype)) {
394 foreach ($doktype as $setup) {
395 if ($setup[1] !== '--div--') {
396 $doktypes[] = [
397 'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
398 'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
399 'count' => (int)$admin->recStats['doktype'][$setup[1]]
400 ];
401 }
402 }
403 }
404
405 // Tables and lost records
406 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
407 $id_list = rtrim($id_list, ',');
408 $admin->lostRecords($id_list);
409 if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
410 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
411 $admin->genTree(0);
412 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
413 $id_list = rtrim($id_list, ',');
414 $admin->lostRecords($id_list);
415 }
416 $tableStatistic = [];
417 $countArr = $admin->countRecords($id_list);
418 if (is_array($GLOBALS['TCA'])) {
419 foreach ($GLOBALS['TCA'] as $t => $value) {
420 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
421 continue;
422 }
423 if ($t === 'pages' && $admin->lostPagesList !== '') {
424 $lostRecordCount = count(explode(',', $admin->lostPagesList));
425 } else {
426 $lostRecordCount = count($admin->lRecords[$t]);
427 }
428 if ($countArr['all'][$t]) {
429 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
430 } else {
431 $theNumberOfRe = '';
432 }
433 $lr = '';
434 if (is_array($admin->lRecords[$t])) {
435 foreach ($admin->lRecords[$t] as $data) {
436 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
437 $lr .= '<div class="record"><a href="' . htmlspecialchars((BackendUtility::getModuleUrl('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>';
438 } else {
439 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
440 }
441 }
442 }
443 $tableStatistic[$t] = [
444 'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
445 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
446 'count' => $theNumberOfRe,
447 'lostRecords' => $lr
448 ];
449 }
450 }
451
452 $this->view->assignMultiple([
453 'pages' => $pageStatistic,
454 'doktypes' => $doktypes,
455 'tables' => $tableStatistic
456 ]);
457 }
458
459 /**
460 * Show list references
461 */
462 protected function func_relations()
463 {
464 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
465 $fkey_arrays = $admin->getGroupFields('');
466 $admin->selectNonEmptyRecordsWithFkeys($fkey_arrays);
467 $fileTest = $admin->testFileRefs();
468
469 if (is_array($fileTest['noFile'])) {
470 ksort($fileTest['noFile']);
471 }
472 $this->view->assignMultiple([
473 'files' => $fileTest,
474 'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
475 'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
476 ]);
477 }
478
479 /**
480 * Returns the Language Service
481 * @return LanguageService
482 */
483 protected function getLanguageService()
484 {
485 return $GLOBALS['LANG'];
486 }
487
488 /**
489 * @return PageRenderer
490 */
491 protected function getPageRenderer()
492 {
493 return GeneralUtility::makeInstance(PageRenderer::class);
494 }
495 }