[TASK] Create own response instance in controller actions
[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\Http\HtmlResponse;
25 use TYPO3\CMS\Core\Imaging\Icon;
26 use TYPO3\CMS\Core\Imaging\IconFactory;
27 use TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck;
28 use TYPO3\CMS\Core\Localization\LanguageService;
29 use TYPO3\CMS\Core\Page\PageRenderer;
30 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32 use TYPO3\CMS\Core\Utility\PathUtility;
33 use TYPO3\CMS\Fluid\View\StandaloneView;
34
35 /**
36 * Script class for the DB int module
37 */
38 class DatabaseIntegrityController
39 {
40 /**
41 * @var string
42 */
43 protected $formName = 'queryform';
44
45 /**
46 * The name of the module
47 *
48 * @var string
49 */
50 protected $moduleName = 'system_dbint';
51
52 /**
53 * @var StandaloneView
54 */
55 protected $view;
56
57 /**
58 * @var string
59 */
60 protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
61
62 /**
63 * @var IconFactory
64 */
65 protected $iconFactory;
66
67 /**
68 * ModuleTemplate Container
69 *
70 * @var ModuleTemplate
71 */
72 protected $moduleTemplate;
73
74 /**
75 * Loaded with the global array $MCONF which holds some module configuration from the conf.php file of backend modules.
76 *
77 * @see init()
78 * @var array
79 */
80 protected $MCONF = [
81 'name' => 'system_dbint',
82 ];
83
84 /**
85 * The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
86 *
87 * @see init()
88 * @var array
89 */
90 protected $MOD_MENU = [
91 'function' => []
92 ];
93
94 /**
95 * Current settings for the keys of the MOD_MENU array
96 *
97 * @see $MOD_MENU
98 * @var array
99 */
100 protected $MOD_SETTINGS = [];
101
102 /**
103 * Injects the request object for the current request or subrequest
104 * Simply calls main() and init() and outputs the content
105 *
106 * @param ServerRequestInterface $request the current request
107 * @return ResponseInterface the response with the content
108 */
109 public function mainAction(ServerRequestInterface $request): ResponseInterface
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 return new HtmlResponse($this->moduleTemplate->renderContent());
167 }
168
169 /**
170 * Configure menu
171 */
172 protected function menuConfig()
173 {
174 $lang = $this->getLanguageService();
175 // MENU-ITEMS:
176 // If array, then it's a selector box menu
177 // If empty string it's just a variable, that'll be saved.
178 // Values NOT in this array will not be saved in the settings-array for the module.
179 $this->MOD_MENU = [
180 'function' => [
181 0 => htmlspecialchars($lang->getLL('menuTitle')),
182 'records' => htmlspecialchars($lang->getLL('recordStatistics')),
183 'relations' => htmlspecialchars($lang->getLL('databaseRelations')),
184 'search' => htmlspecialchars($lang->getLL('fullSearch')),
185 'refindex' => htmlspecialchars($lang->getLL('manageRefIndex'))
186 ],
187 'search' => [
188 'raw' => htmlspecialchars($lang->getLL('rawSearch')),
189 'query' => htmlspecialchars($lang->getLL('advancedQuery'))
190 ],
191 'search_query_smallparts' => '',
192 'search_result_labels' => '',
193 'labels_noprefix' => '',
194 'options_sortlabel' => '',
195 'show_deleted' => '',
196 'queryConfig' => '',
197 // Current query
198 'queryTable' => '',
199 // Current table
200 'queryFields' => '',
201 // Current tableFields
202 'queryLimit' => '',
203 // Current limit
204 'queryOrder' => '',
205 // Current Order field
206 'queryOrderDesc' => '',
207 // Current Order field descending flag
208 'queryOrder2' => '',
209 // Current Order2 field
210 'queryOrder2Desc' => '',
211 // Current Order2 field descending flag
212 'queryGroup' => '',
213 // Current Group field
214 'storeArray' => '',
215 // Used to store the available Query config memory banks
216 'storeQueryConfigs' => '',
217 // Used to store the available Query configs in memory
218 'search_query_makeQuery' => [
219 'all' => htmlspecialchars($lang->getLL('selectRecords')),
220 'count' => htmlspecialchars($lang->getLL('countResults')),
221 'explain' => htmlspecialchars($lang->getLL('explainQuery')),
222 'csv' => htmlspecialchars($lang->getLL('csvExport'))
223 ],
224 'sword' => ''
225 ];
226 // CLEAN SETTINGS
227 $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, '', $this->moduleName, 'ses');
228 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
229 if (GeneralUtility::_GP('queryConfig')) {
230 $qA = GeneralUtility::_GP('queryConfig');
231 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize($qA)], $this->moduleName, 'ses');
232 }
233 $addConditionCheck = GeneralUtility::_GP('qG_ins');
234 $setLimitToStart = false;
235 foreach ($OLD_MOD_SETTINGS as $key => $val) {
236 if (substr($key, 0, 5) === 'query' && $this->MOD_SETTINGS[$key] != $val && $key !== 'queryLimit' && $key !== 'use_listview') {
237 $setLimitToStart = true;
238 if ($key === 'queryTable' && !$addConditionCheck) {
239 $this->MOD_SETTINGS['queryConfig'] = '';
240 }
241 }
242 if ($key === 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
243 $this->MOD_SETTINGS['queryFields'] = '';
244 }
245 }
246 if ($setLimitToStart) {
247 $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
248 if ($currentLimit[1]) {
249 $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
250 } else {
251 $this->MOD_SETTINGS['queryLimit'] = '0';
252 }
253 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
254 }
255 }
256
257 /**
258 * Generates the action menu
259 */
260 protected function getModuleMenu()
261 {
262 $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
263 $menu->setIdentifier('DatabaseJumpMenu');
264 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
265 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
266 foreach ($this->MOD_MENU['function'] as $controller => $title) {
267 $item = $menu
268 ->makeMenuItem()
269 ->setHref(
270 (string)$uriBuilder->buildUriFromRoute(
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 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
297 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
298 foreach ($availableModFuncs as $modFunc) {
299 $modules[$modFunc] = (string)$uriBuilder->buildUriFromRoute('system_dbint') . '&SET[function]=' . $modFunc;
300 }
301 $this->view->assign('availableFunctions', $modules);
302 }
303
304 /****************************
305 *
306 * Functionality implementation
307 *
308 ****************************/
309 /**
310 * Check and update reference index!
311 */
312 protected function func_refindex()
313 {
314 $readmeLocation = ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
315 $this->view->assign('ReadmeLink', PathUtility::getAbsoluteWebPath($readmeLocation));
316 $this->view->assign('ReadmeLocation', $readmeLocation);
317 $this->view->assign('binaryPath', ExtensionManagementUtility::extPath('core', 'bin/typo3'));
318
319 if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
320 $testOnly = (bool)GeneralUtility::_GP('_check');
321 // Call the functionality
322 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
323 $refIndexObj->enableRuntimeCache();
324 list(, $bodyContent) = $refIndexObj->updateIndex($testOnly);
325 $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
326 }
327 }
328
329 /**
330 * Search (Full / Advanced)
331 */
332 protected function func_search()
333 {
334 $lang = $this->getLanguageService();
335 $searchMode = $this->MOD_SETTINGS['search'];
336 $fullsearch = GeneralUtility::makeInstance(QueryView::class, $this->MOD_SETTINGS, $this->MOD_MENU, $this->moduleName);
337 $fullsearch->setFormName($this->formName);
338 $submenu = '<div class="form-inline form-inline-spaced">';
339 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
340 if ($this->MOD_SETTINGS['search'] === 'query') {
341 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
342 }
343 $submenu .= '</div>';
344 if ($this->MOD_SETTINGS['search'] === 'query') {
345 $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>';
346 $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>';
347 $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>';
348 $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>';
349 $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>';
350 }
351 $this->view->assign('submenu', $submenu);
352 $this->view->assign('searchMode', $searchMode);
353 switch ($searchMode) {
354 case 'query':
355 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Lowlevel/QueryGenerator');
356 $this->view->assign('queryMaker', $fullsearch->queryMaker());
357 break;
358 case 'raw':
359 default:
360 $this->view->assign('searchOptions', $fullsearch->form());
361 $this->view->assign('results', $fullsearch->search());
362 }
363 }
364
365 /**
366 * Records overview
367 */
368 protected function func_records()
369 {
370 /** @var $admin DatabaseIntegrityCheck */
371 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
372 $admin->genTree(0);
373
374 // Pages stat
375 $pageStatistic = [
376 'total_pages' => [
377 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
378 'count' => count($admin->page_idArray)
379 ],
380 'translated_pages' => [
381 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
382 'count' => count($admin->getPageTranslatedPageIDArray()),
383 ],
384 'hidden_pages' => [
385 'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
386 'count' => $admin->recStats['hidden']
387 ],
388 'deleted_pages' => [
389 'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
390 'count' => isset($admin->recStats['deleted']['pages']) ? count($admin->recStats['deleted']['pages']) : 0
391 ]
392 ];
393
394 $lang = $this->getLanguageService();
395
396 // Doktype
397 $doktypes = [];
398 $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
399 if (is_array($doktype)) {
400 foreach ($doktype as $setup) {
401 if ($setup[1] !== '--div--') {
402 $doktypes[] = [
403 'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
404 'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
405 'count' => (int)$admin->recStats['doktype'][$setup[1]]
406 ];
407 }
408 }
409 }
410
411 // Tables and lost records
412 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
413 $id_list = rtrim($id_list, ',');
414 $admin->lostRecords($id_list);
415 if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
416 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
417 $admin->genTree(0);
418 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
419 $id_list = rtrim($id_list, ',');
420 $admin->lostRecords($id_list);
421 }
422 $tableStatistic = [];
423 $countArr = $admin->countRecords($id_list);
424 if (is_array($GLOBALS['TCA'])) {
425 foreach ($GLOBALS['TCA'] as $t => $value) {
426 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
427 continue;
428 }
429 if ($t === 'pages' && $admin->lostPagesList !== '') {
430 $lostRecordCount = count(explode(',', $admin->lostPagesList));
431 } else {
432 $lostRecordCount = isset($admin->lRecords[$t]) ? count($admin->lRecords[$t]) : 0;
433 }
434 if ($countArr['all'][$t]) {
435 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
436 } else {
437 $theNumberOfRe = '';
438 }
439 $lr = '';
440 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
441 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
442 if (is_array($admin->lRecords[$t])) {
443 foreach ($admin->lRecords[$t] as $data) {
444 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
445 $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>';
446 } else {
447 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
448 }
449 }
450 }
451 $tableStatistic[$t] = [
452 'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
453 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
454 'count' => $theNumberOfRe,
455 'lostRecords' => $lr
456 ];
457 }
458 }
459
460 $this->view->assignMultiple([
461 'pages' => $pageStatistic,
462 'doktypes' => $doktypes,
463 'tables' => $tableStatistic
464 ]);
465 }
466
467 /**
468 * Show list references
469 */
470 protected function func_relations()
471 {
472 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
473 $fkey_arrays = $admin->getGroupFields('');
474 $admin->selectNonEmptyRecordsWithFkeys($fkey_arrays);
475 $fileTest = $admin->testFileRefs();
476
477 if (is_array($fileTest['noFile'])) {
478 ksort($fileTest['noFile']);
479 }
480 $this->view->assignMultiple([
481 'files' => $fileTest,
482 'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
483 'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
484 ]);
485 }
486
487 /**
488 * Returns the Language Service
489 * @return LanguageService
490 */
491 protected function getLanguageService()
492 {
493 return $GLOBALS['LANG'];
494 }
495
496 /**
497 * @return PageRenderer
498 */
499 protected function getPageRenderer()
500 {
501 return GeneralUtility::makeInstance(PageRenderer::class);
502 }
503 }