a7ff17ab97661126ae2427132fa9ef3580fd58e6
[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\Core\Utility\PathUtility;
31 use TYPO3\CMS\Fluid\View\StandaloneView;
32
33 /**
34 * Script class for the DB int module
35 */
36 class DatabaseIntegrityView extends BaseScriptClass
37 {
38 /**
39 * @var string
40 */
41 protected $formName = 'queryform';
42
43 /**
44 * The name of the module
45 *
46 * @var string
47 */
48 protected $moduleName = 'system_dbint';
49
50 /**
51 * @var StandaloneView
52 */
53 protected $view;
54
55 /**
56 * @var string
57 */
58 protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
59
60 /**
61 * @var IconFactory
62 */
63 protected $iconFactory;
64
65 /**
66 * ModuleTemplate Container
67 *
68 * @var ModuleTemplate
69 */
70 protected $moduleTemplate;
71
72 /**
73 * Constructor
74 */
75 public function __construct()
76 {
77 $this->getLanguageService()->includeLLFile('EXT:lowlevel/Resources/Private/Language/locallang.xlf');
78 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
79 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
80 $this->view->getRequest()->setControllerExtensionName('lowlevel');
81 }
82
83 /**
84 * Initialization
85 *
86 * @return void
87 */
88 public function init()
89 {
90 $this->MCONF['name'] = $this->moduleName;
91 $this->menuConfig();
92 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
93 $this->moduleTemplate->addJavaScriptCode(
94 'jumpToUrl',
95 '
96 function jumpToUrl(URL) {
97 window.location.href = URL;
98 return false;
99 }
100 '
101 );
102 }
103
104 /**
105 * Configure menu
106 *
107 * @return void
108 */
109 public function menuConfig()
110 {
111 $lang = $this->getLanguageService();
112 // MENU-ITEMS:
113 // If array, then it's a selector box menu
114 // If empty string it's just a variable, that'll be saved.
115 // Values NOT in this array will not be saved in the settings-array for the module.
116 $this->MOD_MENU = [
117 'function' => [
118 0 => htmlspecialchars($lang->getLL('menuTitle')),
119 'records' => htmlspecialchars($lang->getLL('recordStatistics')),
120 'relations' => htmlspecialchars($lang->getLL('databaseRelations')),
121 'search' => htmlspecialchars($lang->getLL('fullSearch')),
122 'refindex' => htmlspecialchars($lang->getLL('manageRefIndex'))
123 ],
124 'search' => [
125 'raw' => htmlspecialchars($lang->getLL('rawSearch')),
126 'query' => htmlspecialchars($lang->getLL('advancedQuery'))
127 ],
128 'search_query_smallparts' => '',
129 'search_result_labels' => '',
130 'labels_noprefix' => '',
131 'options_sortlabel' => '',
132 'show_deleted' => '',
133 'queryConfig' => '',
134 // Current query
135 'queryTable' => '',
136 // Current table
137 'queryFields' => '',
138 // Current tableFields
139 'queryLimit' => '',
140 // Current limit
141 'queryOrder' => '',
142 // Current Order field
143 'queryOrderDesc' => '',
144 // Current Order field descending flag
145 'queryOrder2' => '',
146 // Current Order2 field
147 'queryOrder2Desc' => '',
148 // Current Order2 field descending flag
149 'queryGroup' => '',
150 // Current Group field
151 'storeArray' => '',
152 // Used to store the available Query config memory banks
153 'storeQueryConfigs' => '',
154 // Used to store the available Query configs in memory
155 'search_query_makeQuery' => [
156 'all' => htmlspecialchars($lang->getLL('selectRecords')),
157 'count' => htmlspecialchars($lang->getLL('countResults')),
158 'explain' => htmlspecialchars($lang->getLL('explainQuery')),
159 'csv' => htmlspecialchars($lang->getLL('csvExport'))
160 ],
161 'sword' => ''
162 ];
163 // CLEAN SETTINGS
164 $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, '', $this->moduleName, 'ses');
165 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
166 if (GeneralUtility::_GP('queryConfig')) {
167 $qA = GeneralUtility::_GP('queryConfig');
168 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize($qA)], $this->moduleName, 'ses');
169 }
170 $addConditionCheck = GeneralUtility::_GP('qG_ins');
171 $setLimitToStart = false;
172 foreach ($OLD_MOD_SETTINGS as $key => $val) {
173 if (substr($key, 0, 5) == 'query' && $this->MOD_SETTINGS[$key] != $val && $key != 'queryLimit' && $key != 'use_listview') {
174 $setLimitToStart = true;
175 if ($key == 'queryTable' && !$addConditionCheck) {
176 $this->MOD_SETTINGS['queryConfig'] = '';
177 }
178 }
179 if ($key == 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
180 $this->MOD_SETTINGS['queryFields'] = '';
181 }
182 }
183 if ($setLimitToStart) {
184 $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
185 if ($currentLimit[1]) {
186 $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
187 } else {
188 $this->MOD_SETTINGS['queryLimit'] = '0';
189 }
190 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
191 }
192 }
193
194 /**
195 * Main functions, is rendering the content
196 *
197 * @return void
198 */
199 public function main()
200 {
201 switch ($this->MOD_SETTINGS['function']) {
202 case 'search':
203 $templateFilename = 'CustomSearch.html';
204 $this->func_search();
205 break;
206 case 'records':
207 $templateFilename = 'RecordStatistics.html';
208 $this->func_records();
209 break;
210 case 'relations':
211 $templateFilename = 'Relations.html';
212 $this->func_relations();
213 break;
214 case 'refindex':
215 $templateFilename = 'ReferenceIndex.html';
216 $this->func_refindex();
217 break;
218 default:
219 $templateFilename = 'IntegrityOverview.html';
220 $this->func_default();
221 }
222 $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . $templateFilename));
223 $this->content = '<form action="" method="post" id="DatabaseIntegrityView" name="' . $this->formName . '">';
224 $this->content .= $this->view->render();
225 $this->content .= '</form>';
226
227 // Setting up the shortcut button for docheader
228 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
229 // Shortcut
230 $shortCutButton = $buttonBar->makeShortcutButton()
231 ->setModuleName($this->moduleName)
232 ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
233 ->setSetVariables(['function', 'search', 'search_query_makeQuery']);
234 $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
235
236 $this->getModuleMenu();
237 }
238
239 /**
240 * Injects the request object for the current request or subrequest
241 * Simply calls main() and init() and outputs the content
242 *
243 * @param ServerRequestInterface $request the current request
244 * @param ResponseInterface $response
245 * @return ResponseInterface the response with the content
246 */
247 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
248 {
249 $GLOBALS['SOBE'] = $this;
250 $this->init();
251 $this->main();
252
253 $this->moduleTemplate->setContent($this->content);
254 $response->getBody()->write($this->moduleTemplate->renderContent());
255 return $response;
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' => $this->id,
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 * @return void
293 */
294 protected function func_default()
295 {
296 $modules = [];
297 $availableModFuncs = ['records', 'relations', 'search', 'refindex'];
298 foreach ($availableModFuncs as $modFunc) {
299 $modules[$modFunc] = BackendUtility::getModuleUrl('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 * @return void
313 */
314 public function func_refindex()
315 {
316 $readmeLocation = ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
317 $this->view->assign('ReadmeLink', PathUtility::getAbsoluteWebPath($readmeLocation));
318 $this->view->assign('ReadmeLocation', $readmeLocation);
319 $this->view->assign('binaryPath', ExtensionManagementUtility::extPath('core', 'bin/typo3'));
320
321 if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
322 $testOnly = (bool)GeneralUtility::_GP('_check');
323 // Call the functionality
324 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
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 * @return void
334 */
335 public function func_search()
336 {
337 $lang = $this->getLanguageService();
338 $searchMode = $this->MOD_SETTINGS['search'];
339 $fullsearch = GeneralUtility::makeInstance(QueryView::class);
340 $fullsearch->setFormName($this->formName);
341 $submenu = '<div class="form-inline form-inline-spaced">';
342 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
343 if ($this->MOD_SETTINGS['search'] == 'query') {
344 $submenu .= BackendUtility::getDropdownMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
345 }
346 $submenu .= '</div>';
347 if ($this->MOD_SETTINGS['search'] == 'query') {
348 $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>';
349 $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>';
350 $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>';
351 $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>';
352 $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>';
353 }
354 $this->view->assign('submenu', $submenu);
355 $this->view->assign('searchMode', $searchMode);
356 switch ($searchMode) {
357 case 'query':
358 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Lowlevel/QueryGenerator');
359 $this->view->assign('queryMaker', $fullsearch->queryMaker());
360 break;
361 case 'raw':
362 default:
363 $this->view->assign('searchOptions', $fullsearch->form());
364 $this->view->assign('results', $fullsearch->search());
365 }
366 }
367
368 /**
369 * Records overview
370 *
371 * @return void
372 */
373 public function func_records()
374 {
375 /** @var $admin DatabaseIntegrityCheck */
376 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
377 $admin->genTree(0);
378
379 // Pages stat
380 $pageStatistic = [
381 'total_pages' => [
382 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
383 'count' => count($admin->page_idArray)
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' => count($admin->recStats['deleted']['pages'])
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 = count($admin->lRecords[$t]);
434 }
435 if ($countArr['all'][$t]) {
436 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
437 } else {
438 $theNumberOfRe = '';
439 }
440 $lr = '';
441 if (is_array($admin->lRecords[$t])) {
442 foreach ($admin->lRecords[$t] as $data) {
443 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
444 $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>';
445 } else {
446 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
447 }
448 }
449 }
450 $tableStatistic[$t] = [
451 'icon' => $this->iconFactory->getIconForRecord($t, [], Icon::SIZE_SMALL)->render(),
452 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
453 'count' => $theNumberOfRe,
454 'lostRecords' => $lr
455 ];
456 }
457 }
458
459 $this->view->assignMultiple([
460 'pages' => $pageStatistic,
461 'doktypes' => $doktypes,
462 'tables' => $tableStatistic
463 ]);
464 }
465
466 /**
467 * Show list references
468 *
469 * @return void
470 */
471 public 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 ModuleTemplate container
490 *
491 * @return ModuleTemplate
492 */
493 public function getModuleTemplate()
494 {
495 return $this->moduleTemplate;
496 }
497 }