a3bea64886ca83e8e4226fe9fc1ef86c70153bfb
[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 TYPO3\CMS\Backend\Module\BaseScriptClass;
18 use TYPO3\CMS\Backend\Template\DocumentTemplate;
19 use TYPO3\CMS\Backend\Utility\IconUtility;
20 use TYPO3\CMS\Core\Database\QueryView;
21 use TYPO3\CMS\Core\Database\ReferenceIndex;
22 use TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\CMS\Fluid\View\StandaloneView;
26
27 /**
28 * Script class for the DB int module
29 *
30 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
31 */
32 class DatabaseIntegrityView extends BaseScriptClass {
33
34 /**
35 * @var string
36 */
37 protected $formName = 'queryform';
38
39 /**
40 * The name of the module
41 *
42 * @var string
43 */
44 protected $moduleName = 'system_dbint';
45
46 /**
47 * @var StandaloneView
48 */
49 protected $view;
50
51 /**
52 * @var string
53 */
54 protected $templatePath = 'EXT:lowlevel/Resources/Private/Templates/Backend/';
55
56 /**
57 * Constructor
58 */
59 public function __construct() {
60 $this->getLanguageService()->includeLLFile('EXT:lowlevel/Resources/Private/Language/locallang.xlf');
61 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
62 $this->view->getRequest()->setControllerExtensionName('lowlevel');
63 }
64
65 /**
66 * Initialization
67 *
68 * @return void
69 */
70 public function init() {
71 $this->MCONF['name'] = 'system_dbint';
72 $this->menuConfig();
73 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
74 $this->doc->backPath = $GLOBALS['BACK_PATH'];
75 $this->doc->setModuleTemplate('EXT:lowlevel/Resources/Private/Templates/dbint.html');
76 $this->doc->form = '<form action="" method="post" name="' . $this->formName . '">';
77 }
78
79 /**
80 * Configure menu
81 *
82 * @return void
83 */
84 public function menuConfig() {
85 $lang = $this->getLanguageService();
86 // MENU-ITEMS:
87 // If array, then it's a selector box menu
88 // If empty string it's just a variable, that'll be saved.
89 // Values NOT in this array will not be saved in the settings-array for the module.
90 $this->MOD_MENU = array(
91 'function' => array(
92 0 => $lang->getLL('menuTitle', TRUE),
93 'records' => $lang->getLL('recordStatistics', TRUE),
94 'relations' => $lang->getLL('databaseRelations', TRUE),
95 'search' => $lang->getLL('fullSearch', TRUE),
96 'refindex' => $lang->getLL('manageRefIndex', TRUE)
97 ),
98 'search' => array(
99 'raw' => $lang->getLL('rawSearch', TRUE),
100 'query' => $lang->getLL('advancedQuery', TRUE)
101 ),
102 'search_query_smallparts' => '',
103 'search_result_labels' => '',
104 'labels_noprefix' => '',
105 'options_sortlabel' => '',
106 'show_deleted' => '',
107 'queryConfig' => '',
108 // Current query
109 'queryTable' => '',
110 // Current table
111 'queryFields' => '',
112 // Current tableFields
113 'queryLimit' => '',
114 // Current limit
115 'queryOrder' => '',
116 // Current Order field
117 'queryOrderDesc' => '',
118 // Current Order field descending flag
119 'queryOrder2' => '',
120 // Current Order2 field
121 'queryOrder2Desc' => '',
122 // Current Order2 field descending flag
123 'queryGroup' => '',
124 // Current Group field
125 'storeArray' => '',
126 // Used to store the available Query config memory banks
127 'storeQueryConfigs' => '',
128 // Used to store the available Query configs in memory
129 'search_query_makeQuery' => array(
130 'all' => $lang->getLL('selectRecords', TRUE),
131 'count' => $lang->getLL('countResults', TRUE),
132 'explain' => $lang->getLL('explainQuery', TRUE),
133 'csv' => $lang->getLL('csvExport', TRUE)
134 ),
135 'sword' => ''
136 );
137 // CLEAN SETTINGS
138 $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, '', $this->moduleName, 'ses');
139 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName, 'ses');
140 if (GeneralUtility::_GP('queryConfig')) {
141 $qA = GeneralUtility::_GP('queryConfig');
142 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, array('queryConfig' => serialize($qA)), $this->moduleName, 'ses');
143 }
144 $addConditionCheck = GeneralUtility::_GP('qG_ins');
145 $setLimitToStart = FALSE;
146 foreach ($OLD_MOD_SETTINGS as $key => $val) {
147 if (substr($key, 0, 5) == 'query' && $this->MOD_SETTINGS[$key] != $val && $key != 'queryLimit' && $key != 'use_listview') {
148 $setLimitToStart = TRUE;
149 if ($key == 'queryTable' && !$addConditionCheck) {
150 $this->MOD_SETTINGS['queryConfig'] = '';
151 }
152 }
153 if ($key == 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
154 $this->MOD_SETTINGS['queryFields'] = '';
155 }
156 }
157 if ($setLimitToStart) {
158 $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit']);
159 if ($currentLimit[1]) {
160 $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
161 } else {
162 $this->MOD_SETTINGS['queryLimit'] = '0';
163 }
164 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
165 }
166 }
167
168 /**
169 * Main functions, is rendering the content
170 *
171 * @return void
172 */
173 public function main() {
174 switch ($this->MOD_SETTINGS['function']) {
175 case 'search':
176 $templateFilename = 'CustomSearch.html';
177 $this->func_search();
178 break;
179 case 'records':
180 $templateFilename = 'RecordStatistics.html';
181 $this->func_records();
182 break;
183 case 'relations':
184 $templateFilename = 'Relations.html';
185 $this->func_relations();
186 break;
187 case 'refindex':
188 $templateFilename = 'ReferenceIndex.html';
189 $this->func_refindex();
190 break;
191 default:
192 $templateFilename = 'IntegrityOverview.html';
193 $this->func_default();
194 }
195 $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . $templateFilename));
196 $this->content = $this->view->render();
197
198 // Setting up the buttons and markers for docheader
199 $docHeaderButtons = $this->getButtons();
200 $markers = array(
201 'CSH' => $docHeaderButtons['csh'],
202 'FUNC_MENU' => $this->getFuncMenu(),
203 'CONTENT' => $this->content
204 );
205 // Build the <body> for the module
206 $this->content = $this->doc->moduleBody(array(), $docHeaderButtons, $markers);
207 // Renders the module page
208 $this->content = $this->doc->render($this->getLanguageService()->getLL('title'), $this->content);
209 }
210
211 /**
212 * Print content
213 *
214 * @return void
215 */
216 public function printContent() {
217 echo $this->content;
218 }
219
220 /**
221 * Create the panel of buttons for submitting the form or otherwise perform operations.
222 *
223 * @return array All available buttons as an assoc. array
224 */
225 protected function getButtons() {
226 $buttons = array(
227 'csh' => '',
228 'shortcut' => ''
229 );
230 // Shortcut
231 if ($this->getBackendUser()->mayMakeShortcut()) {
232 $buttons['shortcut'] = $this->doc->makeShortcutIcon('', 'function,search,search_query_makeQuery', $this->moduleName);
233 }
234 return $buttons;
235 }
236
237 /**
238 * Create the function menu
239 *
240 * @return string HTML of the function menu
241 */
242 protected function getFuncMenu() {
243 return BackendUtility::getFuncMenu(0, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']);
244 }
245
246 /**
247 * Creates the overview menu.
248 *
249 * @return void
250 */
251 protected function func_default() {
252 $modules = array();
253 $availableModFuncs = array('records', 'relations', 'search', 'refindex');
254 foreach ($availableModFuncs as $modFunc) {
255 $modules[$modFunc] = BackendUtility::getModuleUrl('system_dbint') . '&SET[function]=' . $modFunc;
256 }
257 $this->view->assign('availableFunctions', $modules);
258 }
259
260 /****************************
261 *
262 * Functionality implementation
263 *
264 ****************************/
265 /**
266 * Check and update reference index!
267 *
268 * @return void
269 */
270 public function func_refindex() {
271 $this->view->assign('PATH_typo3', PATH_typo3);
272
273 if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
274 $testOnly = GeneralUtility::_GP('_check') ? TRUE : FALSE;
275 // Call the functionality
276 $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
277 list(,$bodyContent) = $refIndexObj->updateIndex($testOnly);
278 $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
279 }
280 }
281
282 /**
283 * Search (Full / Advanced)
284 *
285 * @return void
286 */
287 public function func_search() {
288 $lang = $this->getLanguageService();
289 $searchMode = $this->MOD_SETTINGS['search'];
290 $fullsearch = GeneralUtility::makeInstance(QueryView::class);
291 $fullsearch->setFormName($this->formName);
292 $submenu = BackendUtility::getFuncMenu(0, 'SET[search]', $searchMode, $this->MOD_MENU['search']);
293 if ($this->MOD_SETTINGS['search'] == 'query') {
294 $submenu .= BackendUtility::getFuncMenu(0, 'SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery']) . '<br />';
295 $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>';
296 $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>';
297 $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>';
298 $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>';
299 $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>';
300 }
301 $this->view->assign('submenu', $submenu);
302 $this->view->assign('searchMode', $searchMode);
303 switch ($searchMode) {
304 case 'query':
305 $this->doc->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Core/QueryGenerator');
306 $this->view->assign('queryMaker', $fullsearch->queryMaker());
307 break;
308 case 'raw':
309 default:
310 $this->view->assign('searchOptions', $fullsearch->form());
311 $this->view->assign('results', $fullsearch->search());
312 }
313 }
314
315 /**
316 * Records overview
317 *
318 * @return void
319 */
320 public function func_records() {
321
322 /** @var $admin DatabaseIntegrityCheck */
323 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
324 $admin->backPath = $GLOBALS['BACK_PATH'];
325 $admin->genTree(0);
326
327 // Pages stat
328 $pageStatistic = array(
329 'total_pages' => array(
330 'icon' => IconUtility::getSpriteIconForRecord('pages', array()),
331 'count' => count($admin->page_idArray)
332 ),
333 'hidden_pages' => array(
334 'icon' => IconUtility::getSpriteIconForRecord('pages', array('hidden' => 1)),
335 'count' => $admin->recStats['hidden']
336 ),
337 'deleted_pages' => array(
338 'icon' => IconUtility::getSpriteIconForRecord('pages', array('deleted' => 1)),
339 'count' => count($admin->recStats['deleted']['pages'])
340 )
341 );
342
343 $lang = $this->getLanguageService();
344
345 // Doktype
346 $doktypes = array();
347 $doktype = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
348 if (is_array($doktype)) {
349 foreach ($doktype as $setup) {
350 if ($setup[1] != '--div--') {
351 $doktypes[] = array(
352 'icon' => IconUtility::getSpriteIconForRecord('pages', array('doktype' => $setup[1])),
353 'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
354 'count' => (int)$admin->recStats['doktype'][$setup[1]]
355 );
356 }
357 }
358 }
359
360 // Tables and lost records
361 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
362 $id_list = rtrim($id_list, ',');
363 $admin->lostRecords($id_list);
364 if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
365 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
366 $admin->backPath = $GLOBALS['BACK_PATH'];
367 $admin->genTree(0);
368 $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
369 $id_list = rtrim($id_list, ',');
370 $admin->lostRecords($id_list);
371 }
372 $tableStatistic = array();
373 $countArr = $admin->countRecords($id_list);
374 if (is_array($GLOBALS['TCA'])) {
375 foreach ($GLOBALS['TCA'] as $t => $value) {
376 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
377 continue;
378 }
379 if ($t === 'pages' && $admin->lostPagesList !== '') {
380 $lostRecordCount = count(explode(',', $admin->lostPagesList));
381 } else {
382 $lostRecordCount = count($admin->lRecords[$t]);
383 }
384 if ($countArr['all'][$t]) {
385 $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
386 } else {
387 $theNumberOfRe = '';
388 }
389 $lr = '';
390 if (is_array($admin->lRecords[$t])) {
391 foreach ($admin->lRecords[$t] as $data) {
392 if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
393 $lr .= '<div class="record"><a href="' . htmlspecialchars((BackendUtility::getModuleUrl('system_dbint') . '&SET[function]=records&fixLostRecords_table=' . $t . '&fixLostRecords_uid=' . $data['uid'])) . '">' . IconUtility::getSpriteIcon('status-dialog-error', array('title' => $lang->getLL('fixLostRecord'))) . '</a>uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
394 } else {
395 $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
396 }
397 }
398 }
399 $tableStatistic[$t] = array(
400 'icon' => IconUtility::getSpriteIconForRecord($t, array()),
401 'title' => $lang->sL($GLOBALS['TCA'][$t]['ctrl']['title']),
402 'count' => $theNumberOfRe,
403 'lostRecords' => $lr
404 );
405 }
406 }
407
408 $this->view->assignMultiple(array(
409 'pages' => $pageStatistic,
410 'doktypes' => $doktypes,
411 'tables' => $tableStatistic
412 ));
413 }
414
415 /**
416 * Show list references
417 *
418 * @return void
419 */
420 public function func_relations() {
421 $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
422 $admin->backPath = $GLOBALS['BACK_PATH'];
423 $fkey_arrays = $admin->getGroupFields('');
424 $admin->selectNonEmptyRecordsWithFkeys($fkey_arrays);
425 $fileTest = $admin->testFileRefs();
426
427 if (is_array($fileTest['noFile'])) {
428 ksort($fileTest['noFile']);
429 }
430 $this->view->assignMultiple(array(
431 'files' => $fileTest,
432 'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
433 'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
434 ));
435 }
436
437 }