c53b51c463639662394bf757baf4cbc7d13706fa
[Packages/TYPO3.CMS.git] / typo3 / sysext / lowlevel / Classes / View / DatabaseIntegrityView.php
1 <?php
2 namespace TYPO3\CMS\Lowlevel\View;
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\Module\BaseScriptClass;
20 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Database\QueryView;
24 use TYPO3\CMS\Core\Database\ReferenceIndex;
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\Utility\ExtensionManagementUtility;
29 use TYPO3\CMS\Core\Utility\GeneralUtility;
30 use TYPO3\CMS\Fluid\View\StandaloneView;
31
32 /**
33 * Script class for the DB int module
34 */
35 class DatabaseIntegrityView extends BaseScriptClass
36 {
37 /**
38 * @var string
39 */
40 protected $formName = 'queryform';
41
42 /**
43 * The name of the module
44 *
45 * @var string
46 */
47 protected $moduleName = 'system_dbint';
48
49 /**
50 * @var StandaloneView
51 */
52 protected $view;
53
54 /**
55 * @var string
56 */
57 protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
58
59 /**
60 * @var IconFactory
61 */
62 protected $iconFactory;
63
64 /**
65 * ModuleTemplate Container
66 *
67 * @var ModuleTemplate
68 */
69 protected $moduleTemplate;
70
71 /**
72 * Constructor
73 */
74 public function __construct()
75 {
76 $this->getLanguageService()->includeLLFile('EXT:lowlevel/Resources/Private/Language/locallang.xlf');
77 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
78 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
79 $this->view->getRequest()->setControllerExtensionName('lowlevel');
80 }
81
82 /**
83 * Initialization
84 *
85 * @return void
86 */
87 public function init()
88 {
89 $this->MCONF['name'] = $this->moduleName;
90 $this->menuConfig();
91 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
92 $this->moduleTemplate->addJavaScriptCode(
93 'jumpToUrl',
94 '
95 function jumpToUrl(URL) {
96 window.location.href = URL;
97 return false;
98 }
99 '
100 );
101 }
102
103 /**
104 * Configure menu
105 *
106 * @return void
107 */
108 public function menuConfig()
109 {
110 $lang = $this->getLanguageService();
111 // MENU-ITEMS:
112 // If array, then it's a selector box menu
113 // If empty string it's just a variable, that'll be saved.
114 // Values NOT in this array will not be saved in the settings-array for the module.
115 $this->MOD_MENU = [
116 'function' => [
117 0 => htmlspecialchars($lang->getLL('menuTitle')),
118 'records' => htmlspecialchars($lang->getLL('recordStatistics')),
119 'relations' => htmlspecialchars($lang->getLL('databaseRelations')),
120 'search' => htmlspecialchars($lang->getLL('fullSearch')),
121 'refindex' => htmlspecialchars($lang->getLL('manageRefIndex'))
122 ],
123 'search' => [
124 'raw' => htmlspecialchars($lang->getLL('rawSearch')),
125 'query' => htmlspecialchars($lang->getLL('advancedQuery'))
126 ],
127 'search_query_smallparts' => '',
128 'search_result_labels' => '',
129 'labels_noprefix' => '',
130 'options_sortlabel' => '',
131 'show_deleted' => '',
132 'queryConfig' => '',
133 // Current query
134 'queryTable' => '',
135 // Current table
136 'queryFields' => '',
137 // Current tableFields
138 'queryLimit' => '',
139 // Current limit
140 'queryOrder' => '',
141 // Current Order field
142 'queryOrderDesc' => '',
143 // Current Order field descending flag
144 'queryOrder2' => '',
145 // Current Order2 field
146 'queryOrder2Desc' => '',
147 // Current Order2 field descending flag
148 'queryGroup' => '',
149 // Current Group field
150 'storeArray' => '',
151 // Used to store the available Query config memory banks
152 'storeQueryConfigs' => '',
153 // Used to store the available Query configs in memory
154 'search_query_makeQuery' => [
155 'all' => htmlspecialchars($lang->getLL('selectRecords')),
156 'count' => htmlspecialchars($lang->getLL('countResults')),
157 'explain' => htmlspecialchars($lang->getLL('explainQuery')),
158 'csv' => htmlspecialchars($lang->getLL('csvExport'))
159 ],
160 'sword' => ''
161 ];
162 // CLEAN SETTINGS
163 $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, '', $this->moduleName, 'ses');
164 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
165 if (GeneralUtility::_GP('queryConfig')) {
166 $qA = GeneralUtility::_GP('queryConfig');
167 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize($qA)], $this->moduleName, 'ses');
168 }
169 $addConditionCheck = GeneralUtility::_GP('qG_ins');
170 $setLimitToStart = false;
171 foreach ($OLD_MOD_SETTINGS as $key => $val) {
172 if (substr($key, 0, 5) == 'query' && $this->MOD_SETTINGS[$key] != $val && $key != 'queryLimit' && $key != 'use_listview') {
173 $setLimitToStart = true;
174 if ($key == 'queryTable' && !$addConditionCheck) {
175 $this->MOD_SETTINGS['queryConfig'] = '';
176 }
177 }
178 if ($key == 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
179 $this->MOD_SETTINGS['queryFields'] = '';
180 }
181 }
182 if ($setLimitToStart) {
183 $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
184 if ($currentLimit[1]) {
185 $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
186 } else {
187 $this->MOD_SETTINGS['queryLimit'] = '0';
188 }
189 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
190 }
191 }
192
193 /**
194 * Main functions, is rendering the content
195 *
196 * @return void
197 */
198 public function main()
199 {
200 switch ($this->MOD_SETTINGS['function']) {
201 case 'search':
202 $templateFilename = 'CustomSearch.html';
203 $this->func_search();
204 break;
205 case 'records':
206 $templateFilename = 'RecordStatistics.html';
207 $this->func_records();
208 break;
209 case 'relations':
210 $templateFilename = 'Relations.html';
211 $this->func_relations();
212 break;
213 case 'refindex':
214 $templateFilename = 'ReferenceIndex.html';
215 $this->func_refindex();
216 break;
217 default:
218 $templateFilename = 'IntegrityOverview.html';
219 $this->func_default();
220 }
221 $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . $templateFilename));
222 $this->content = '<form action="" method="post" id="DatabaseIntegrityView" name="' . $this->formName . '">';
223 $this->content .= $this->view->render();
224 $this->content .= '</form>';
225
226 // Setting up the shortcut button for docheader
227 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
228 // Shortcut
229 $shortCutButton = $buttonBar->makeShortcutButton()
230 ->setModuleName($this->moduleName)
231 ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
232 ->setSetVariables(['function', 'search', 'search_query_makeQuery']);
233 $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
234
235 $this->getModuleMenu();
236 }
237
238 /**
239 * Injects the request object for the current request or subrequest
240 * Simply calls main() and init() and outputs the content
241 *
242 * @param ServerRequestInterface $request the current request
243 * @param ResponseInterface $response
244 * @return ResponseInterface the response with the content
245 */
246 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
247 {
248 $GLOBALS['SOBE'] = $this;
249 $this->init();
250 $this->main();
251
252 $this->moduleTemplate->setContent($this->content);
253 $response->getBody()->write($this->moduleTemplate->renderContent());
254 return $response;
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
265 foreach ($this->MOD_MENU['function'] as $controller => $title) {
266 $item = $menu
267 ->makeMenuItem()
268 ->setHref(
269 BackendUtility::getModuleUrl(
270 $this->moduleName,
271 [
272 'id' => $this->id,
273 'SET' => [
274 'function' => $controller
275 ]
276 ]
277 )
278 )
279 ->setTitle($title);
280 if ($controller === $this->MOD_SETTINGS['function']) {
281 $item->setActive(true);
282 }
283 $menu->addMenuItem($item);
284 }
285 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
286 }
287
288 /**
289 * Creates the overview menu.
290 *
291 * @return void
292 */
293 protected function func_default()
294 {
295 $modules = [];
296 $availableModFuncs = ['records', 'relations', 'search', 'refindex'];
297 foreach ($availableModFuncs as $modFunc) {
298 $modules[$modFunc] = BackendUtility::getModuleUrl('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 * @return void
312 */
313 public function func_refindex()
314 {
315 $this->view->assign('ReadmeLink', ExtensionManagementUtility::extRelPath('lowlevel') . 'README.rst');
316 $this->view->assign('ReadmeLocation', ExtensionManagementUtility::extPath('lowlevel', 'README.rst'));
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 list(, $bodyContent) = $refIndexObj->updateIndex($testOnly);
324 $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
325 }
326 }
327
328 /**
329 * Search (Full / Advanced)
330 *
331 * @return void
332 */
333 public function func_search()
334 {
335 $lang = $this->getLanguageService();
336 $searchMode = $this->MOD_SETTINGS['search'];
337 $fullsearch = GeneralUtility::makeInstance(QueryView::class);
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($GLOBALS['SOBE']->id, '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($GLOBALS['SOBE']->id, '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($GLOBALS['SOBE']->id, '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($GLOBALS['SOBE']->id, '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($GLOBALS['SOBE']->id, '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 * @return void
370 */
371 public function func_records()
372 {
373 /** @var $admin DatabaseIntegrityCheck */
374 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
375 $admin->genTree(0);
376
377 // Pages stat
378 $pageStatistic = [
379 'total_pages' => [
380 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
381 'count' => count($admin->page_idArray)
382 ],
383 'hidden_pages' => [
384 'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
385 'count' => $admin->recStats['hidden']
386 ],
387 'deleted_pages' => [
388 'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
389 'count' => count($admin->recStats['deleted']['pages'])
390 ]
391 ];
392
393 $lang = $this->getLanguageService();
394
395 // Doktype
396 $doktypes = [];
397 $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
398 if (is_array($doktype)) {
399 foreach ($doktype as $setup) {
400 if ($setup[1] != '--div--') {
401 $doktypes[] = [
402 'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
403 'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
404 'count' => (int)$admin->recStats['doktype'][$setup[1]]
405 ];
406 }
407 }
408 }
409
410 // Tables and lost records
411 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
412 $id_list = rtrim($id_list, ',');
413 $admin->lostRecords($id_list);
414 if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
415 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
416 $admin->genTree(0);
417 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
418 $id_list = rtrim($id_list, ',');
419 $admin->lostRecords($id_list);
420 }
421 $tableStatistic = [];
422 $countArr = $admin->countRecords($id_list);
423 if (is_array($GLOBALS['TCA'])) {
424 foreach ($GLOBALS['TCA'] as $t => $value) {
425 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
426 continue;
427 }
428 if ($t === 'pages' && $admin->lostPagesList !== '') {
429 $lostRecordCount = count(explode(',', $admin->lostPagesList));
430 } else {
431 $lostRecordCount = count($admin->lRecords[$t]);
432 }
433 if ($countArr['all'][$t]) {
434 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
435 } else {
436 $theNumberOfRe = '';
437 }
438 $lr = '';
439 if (is_array($admin->lRecords[$t])) {
440 foreach ($admin->lRecords[$t] as $data) {
441 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
442 $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>';
443 } else {
444 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
445 }
446 }
447 }
448 $tableStatistic[$t] = [
449 'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
450 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
451 'count' => $theNumberOfRe,
452 'lostRecords' => $lr
453 ];
454 }
455 }
456
457 $this->view->assignMultiple([
458 'pages' => $pageStatistic,
459 'doktypes' => $doktypes,
460 'tables' => $tableStatistic
461 ]);
462 }
463
464 /**
465 * Show list references
466 *
467 * @return void
468 */
469 public function func_relations()
470 {
471 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
472 $fkey_arrays = $admin->getGroupFields('');
473 $admin->selectNonEmptyRecordsWithFkeys($fkey_arrays);
474 $fileTest = $admin->testFileRefs();
475
476 if (is_array($fileTest['noFile'])) {
477 ksort($fileTest['noFile']);
478 }
479 $this->view->assignMultiple([
480 'files' => $fileTest,
481 'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
482 'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
483 ]);
484 }
485
486 /**
487 * Returns the ModuleTemplate container
488 *
489 * @return ModuleTemplate
490 */
491 public function getModuleTemplate()
492 {
493 return $this->moduleTemplate;
494 }
495 }