TYPO3 CMS  TYPO3_7-6
DatabaseIntegrityCheck.php
Go to the documentation of this file.
1 <?php
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 
18 
28 {
32  public $genTree_includeDeleted = true;
33 
37  public $genTree_includeVersions = true;
38 
42  public $genTree_includeRecords = false;
43 
47  public $perms_clause = '';
48 
52  public $page_idArray = [];
53 
57  public $rec_idArray = [];
58 
62  public $checkFileRefs = [];
63 
67  public $checkSelectDBRefs = [];
68 
72  public $checkGroupDBRefs = [];
73 
77  public $recStats = [
78  'allValid' => [],
79  'published_versions' => [],
80  'deleted' => []
81  ];
82 
86  public $lRecords = [];
87 
91  public $lostPagesList = '';
92 
102  public function genTree($theID, $depthData = '', $versions = false)
103  {
104  if ($versions) {
105  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,title,doktype,deleted,t3ver_wsid,t3ver_id,t3ver_count,hidden', 'pages', 'pid=-1 AND t3ver_oid=' . (int)$theID . ' ' . (!$this->genTree_includeDeleted ? 'AND deleted=0' : '') . $this->perms_clause, '', 'sorting');
106  } else {
107  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,title,doktype,deleted,hidden', 'pages', 'pid=' . (int)$theID . ' ' . (!$this->genTree_includeDeleted ? 'AND deleted=0' : '') . $this->perms_clause, '', 'sorting');
108  }
109  // Traverse the records selected:
110  $a = 0;
111  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
112  $a++;
113  $newID = $row['uid'];
114  // Register various data for this item:
115  $this->page_idArray[$newID] = $row;
116  $this->recStats['all_valid']['pages'][$newID] = $newID;
117  if ($row['deleted']) {
118  $this->recStats['deleted']['pages'][$newID] = $newID;
119  }
120  if ($versions && $row['t3ver_count'] >= 1) {
121  $this->recStats['published_versions']['pages'][$newID] = $newID;
122  }
123  if ($row['deleted']) {
124  $this->recStats['deleted']++;
125  }
126  if ($row['hidden']) {
127  $this->recStats['hidden']++;
128  }
129  $this->recStats['doktype'][$row['doktype']]++;
130  // If all records should be shown, do so:
131  if ($this->genTree_includeRecords) {
132  foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
133  if ($tableName != 'pages') {
134  $this->genTree_records($newID, '', $tableName);
135  }
136  }
137  }
138  // Add sub pages:
139  $this->genTree($newID);
140  // If versions are included in the tree, add those now:
141  if ($this->genTree_includeVersions) {
142  $this->genTree($newID, '', true);
143  }
144  }
145  $GLOBALS['TYPO3_DB']->sql_free_result($res);
146  }
147 
155  public function genTree_records($theID, $_ = '', $table = '', $versions = false)
156  {
157  if ($versions) {
158  // Select all records from table pointing to this page:
159  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
161  $table,
162  'pid=-1 AND t3ver_oid=' . (int)$theID . (!$this->genTree_includeDeleted ? BackendUtility::deleteClause($table) : '')
163  );
164  } else {
165  // Select all records from table pointing to this page:
166  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
168  $table,
169  'pid=' . (int)$theID . (!$this->genTree_includeDeleted ? BackendUtility::deleteClause($table) : '')
170  );
171  }
172  // Traverse selected:
173  $a = 0;
174  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
175  $a++;
176  $newID = $row['uid'];
177  // Register various data for this item:
178  $this->rec_idArray[$table][$newID] = $row;
179  $this->recStats['all_valid'][$table][$newID] = $newID;
180  if ($row['deleted']) {
181  $this->recStats['deleted'][$table][$newID] = $newID;
182  }
183  if ($versions && $row['t3ver_count'] >= 1 && $row['t3ver_wsid'] == 0) {
184  $this->recStats['published_versions'][$table][$newID] = $newID;
185  }
186  // Select all versions of this record:
187  if ($this->genTree_includeVersions && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
188  $this->genTree_records($newID, '', $table, true);
189  }
190  }
191  $GLOBALS['TYPO3_DB']->sql_free_result($res);
192  }
193 
200  public function lostRecords($pid_list)
201  {
202  $this->lostPagesList = '';
203  if ($pid_list) {
204  foreach ($GLOBALS['TCA'] as $table => $tableConf) {
205  $pid_list_tmp = $pid_list;
206  if (!isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
207  // Remove preceding "-1," for non-versioned tables
208  $pid_list_tmp = preg_replace('/^\\-1,/', '', $pid_list_tmp);
209  }
210  $garbage = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'], $table, 'pid NOT IN (' . $pid_list_tmp . ')');
211  $lostIdList = [];
212  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($garbage)) {
213  $this->lRecords[$table][$row['uid']] = [
214  'uid' => $row['uid'],
215  'pid' => $row['pid'],
216  'title' => strip_tags(BackendUtility::getRecordTitle($table, $row))
217  ];
218  $lostIdList[] = $row['uid'];
219  }
220  $GLOBALS['TYPO3_DB']->sql_free_result($garbage);
221  if ($table == 'pages') {
222  $this->lostPagesList = implode(',', $lostIdList);
223  }
224  }
225  }
226  }
227 
236  public function fixLostRecord($table, $uid)
237  {
238  if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords[$table][$uid]) && $GLOBALS['BE_USER']->user['admin']) {
239  $updateFields = [];
240  $updateFields['pid'] = 0;
241  // If possible a lost record restored is hidden as default
242  if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
243  $updateFields[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] = 1;
244  }
245  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateFields);
246  return true;
247  } else {
248  return false;
249  }
250  }
251 
258  public function countRecords($pid_list)
259  {
260  $list = [];
261  $list_n = [];
262  if ($pid_list) {
263  foreach ($GLOBALS['TCA'] as $table => $tableConf) {
264  $pid_list_tmp = $pid_list;
265  if (!isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
266  // Remove preceding "-1," for non-versioned tables
267  $pid_list_tmp = preg_replace('/^\\-1,/', '', $pid_list_tmp);
268  }
269  $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid IN (' . $pid_list_tmp . ')');
270  if ($count) {
271  $list[$table] = $count;
272  }
273  $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid IN (' . $pid_list_tmp . ')' . BackendUtility::deleteClause($table));
274  if ($count) {
275  $list_n[$table] = $count;
276  }
277  }
278  }
279  return ['all' => $list, 'non_deleted' => $list_n];
280  }
281 
288  public function getGroupFields($mode)
289  {
290  $result = [];
291  foreach ($GLOBALS['TCA'] as $table => $tableConf) {
292  $cols = $GLOBALS['TCA'][$table]['columns'];
293  foreach ($cols as $field => $config) {
294  if ($config['config']['type'] == 'group') {
295  if ((!$mode || $mode == 'file') && $config['config']['internal_type'] == 'file' || (!$mode || $mode == 'db') && $config['config']['internal_type'] == 'db') {
296  $result[$table][] = $field;
297  }
298  }
299  if ((!$mode || $mode == 'db') && $config['config']['type'] == 'select' && $config['config']['foreign_table']) {
300  $result[$table][] = $field;
301  }
302  }
303  if ($result[$table]) {
304  $result[$table] = implode(',', $result[$table]);
305  }
306  }
307  return $result;
308  }
309 
316  public function getFileFields($uploadfolder)
317  {
318  $result = [];
319  foreach ($GLOBALS['TCA'] as $table => $tableConf) {
320  $cols = $GLOBALS['TCA'][$table]['columns'];
321  foreach ($cols as $field => $config) {
322  if ($config['config']['type'] == 'group' && $config['config']['internal_type'] == 'file' && $config['config']['uploadfolder'] == $uploadfolder) {
323  $result[] = [$table, $field];
324  }
325  }
326  }
327  return $result;
328  }
329 
336  public function getDBFields($theSearchTable)
337  {
338  $result = [];
339  foreach ($GLOBALS['TCA'] as $table => $tableConf) {
340  $cols = $GLOBALS['TCA'][$table]['columns'];
341  foreach ($cols as $field => $config) {
342  if ($config['config']['type'] == 'group' && $config['config']['internal_type'] == 'db') {
343  if (trim($config['config']['allowed']) == '*' || strstr($config['config']['allowed'], $theSearchTable)) {
344  $result[] = [$table, $field];
345  }
346  } elseif ($config['config']['type'] == 'select' && $config['config']['foreign_table'] == $theSearchTable) {
347  $result[] = [$table, $field];
348  }
349  }
350  }
351  return $result;
352  }
353 
361  public function selectNonEmptyRecordsWithFkeys($fkey_arrays)
362  {
363  if (is_array($fkey_arrays)) {
364  foreach ($fkey_arrays as $table => $field_list) {
365  if ($GLOBALS['TCA'][$table] && trim($field_list)) {
366  $fieldArr = explode(',', $field_list);
367  if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
368  $fields = $GLOBALS['TYPO3_DB']->admin_get_fields($table);
369  $field = array_shift($fieldArr);
370  $cl_fl = $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'I' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'N' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'R' ? $field . '<>0' : $field . '<>\'\'';
371  foreach ($fieldArr as $field) {
372  $cl_fl .= $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'I' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'N' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'R' ? ' OR ' . $field . '<>0' : ' OR ' . $field . '<>\'\'';
373  }
374  unset($fields);
375  } else {
376  $cl_fl = implode('<>\'\' OR ', $fieldArr) . '<>\'\'';
377  }
378  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,' . $field_list, $table, $cl_fl);
379  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
380  foreach ($fieldArr as $field) {
381  if (trim($row[$field])) {
382  $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
383  if ($fieldConf['type'] == 'group') {
384  if ($fieldConf['internal_type'] == 'file') {
385  // Files...
386  if ($fieldConf['MM']) {
387  $tempArr = [];
389  $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
390  $dbAnalysis->start('', 'files', $fieldConf['MM'], $row['uid']);
391  foreach ($dbAnalysis->itemArray as $somekey => $someval) {
392  if ($someval['id']) {
393  $tempArr[] = $someval['id'];
394  }
395  }
396  } else {
397  $tempArr = explode(',', trim($row[$field]));
398  }
399  foreach ($tempArr as $file) {
400  $file = trim($file);
401  if ($file) {
402  $this->checkFileRefs[$fieldConf['uploadfolder']][$file] += 1;
403  }
404  }
405  }
406  if ($fieldConf['internal_type'] == 'db') {
408  $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
409  $dbAnalysis->start($row[$field], $fieldConf['allowed'], $fieldConf['MM'], $row['uid'], $table, $fieldConf);
410  foreach ($dbAnalysis->itemArray as $tempArr) {
411  $this->checkGroupDBRefs[$tempArr['table']][$tempArr['id']] += 1;
412  }
413  }
414  }
415  if ($fieldConf['type'] == 'select' && $fieldConf['foreign_table']) {
417  $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
418  $dbAnalysis->start($row[$field], $fieldConf['foreign_table'], $fieldConf['MM'], $row['uid'], $table, $fieldConf);
419  foreach ($dbAnalysis->itemArray as $tempArr) {
420  if ($tempArr['id'] > 0) {
421  $this->checkGroupDBRefs[$fieldConf['foreign_table']][$tempArr['id']] += 1;
422  }
423  }
424  }
425  }
426  }
427  }
428  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
429  }
430  }
431  }
432  }
433 
439  public function testFileRefs()
440  {
441  $output = [];
442  // Handle direct references with upload folder setting (workaround)
443  $newCheckFileRefs = [];
444  foreach ($this->checkFileRefs as $folder => $files) {
445  // Only direct references without a folder setting
446  if ($folder !== '') {
447  $newCheckFileRefs[$folder] = $files;
448  continue;
449  }
450  foreach ($files as $file => $references) {
451  // Direct file references have often many references (removes occurrences in the moreReferences section of the result array)
452  if ($references > 1) {
453  $references = 1;
454  }
455  // The directory must be empty (prevents checking of the root directory)
456  $directory = dirname($file);
457  if ($directory !== '') {
458  $newCheckFileRefs[$directory][basename($file)] = $references;
459  }
460  }
461  }
462  $this->checkFileRefs = $newCheckFileRefs;
463  foreach ($this->checkFileRefs as $folder => $fileArr) {
464  $path = PATH_site . $folder;
465  if (@is_dir($path) && @is_readable($path)) {
466  $d = dir($path);
467  while ($entry = $d->read()) {
468  if (@is_file(($path . '/' . $entry))) {
469  if (isset($fileArr[$entry])) {
470  if ($fileArr[$entry] > 1) {
471  $temp = $this->whereIsFileReferenced($folder, $entry);
472  $tempList = '';
473  foreach ($temp as $inf) {
474  $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
475  }
476  $output['moreReferences'][] = [$path, $entry, $fileArr[$entry], $tempList];
477  }
478  unset($fileArr[$entry]);
479  } else {
480  // Contains workaround for direct references
481  if (!strstr($entry, 'index.htm') && !preg_match(('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/'), $folder)) {
482  $output['noReferences'][] = [$path, $entry];
483  }
484  }
485  }
486  }
487  $d->close();
488  $tempCounter = 0;
489  foreach ($fileArr as $file => $value) {
490  // Workaround for direct file references
491  if (preg_match('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/', $folder)) {
492  $file = $folder . '/' . $file;
493  $folder = '';
494  $path = substr(PATH_site, 0, -1);
495  }
496  $temp = $this->whereIsFileReferenced($folder, $file);
497  $tempList = '';
498  foreach ($temp as $inf) {
499  $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
500  }
501  $tempCounter++;
502  $output['noFile'][substr($path, -3) . '_' . substr($file, 0, 3) . '_' . $tempCounter] = [$path, $file, $tempList];
503  }
504  } else {
505  $output['error'][] = [$path];
506  }
507  }
508  return $output;
509  }
510 
517  public function testDBRefs($theArray)
518  {
519  $result = '';
520  foreach ($theArray as $table => $dbArr) {
521  if ($GLOBALS['TCA'][$table]) {
522  $idlist = array_keys($dbArr);
523  $theList = implode(',', $idlist);
524  if ($theList) {
525  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $theList . ')' . BackendUtility::deleteClause($table));
526  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
527  if (isset($dbArr[$row['uid']])) {
528  unset($dbArr[$row['uid']]);
529  } else {
530  $result .= 'Strange Error. ...<br />';
531  }
532  }
533  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
534  foreach ($dbArr as $theId => $theC) {
535  $result .= 'There are ' . $theC . ' records pointing to this missing or deleted record; [' . $table . '][' . $theId . ']<br />';
536  }
537  }
538  } else {
539  $result .= 'Codeerror. Table is not a table...<br />';
540  }
541  }
542  return $result;
543  }
544 
552  public function whereIsRecordReferenced($searchTable, $id)
553  {
554  // Gets tables / Fields that reference to files
555  $fileFields = $this->getDBFields($searchTable);
556  $theRecordList = [];
557  foreach ($fileFields as $info) {
558  $table = $info[0];
559  $field = $info[1];
560  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'] . ',' . $field, $table, $field . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($id, $table) . '%\'');
561  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
562  // Now this is the field, where the reference COULD come from. But we're not garanteed, so we must carefully examine the data.
563  $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
564  $allowedTables = $fieldConf['type'] == 'group' ? $fieldConf['allowed'] : $fieldConf['foreign_table'];
566  $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
567  $dbAnalysis->start($row[$field], $allowedTables, $fieldConf['MM'], $row['uid'], $table, $fieldConf);
568  foreach ($dbAnalysis->itemArray as $tempArr) {
569  if ($tempArr['table'] == $searchTable && $tempArr['id'] == $id) {
570  $theRecordList[] = ['table' => $table, 'uid' => $row['uid'], 'field' => $field, 'pid' => $row['pid']];
571  }
572  }
573  }
574  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
575  }
576  return $theRecordList;
577  }
578 
586  public function whereIsFileReferenced($uploadfolder, $filename)
587  {
588  // Gets tables / Fields that reference to files
589  $fileFields = $this->getFileFields($uploadfolder);
590  $theRecordList = [];
591  foreach ($fileFields as $info) {
592  $table = $info[0];
593  $field = $info[1];
594  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'] . ',' . $field, $table, $field . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($filename, $table) . '%\'');
595  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
596  // Now this is the field, where the reference COULD come from.
597  // But we're not guaranteed, so we must carefully examine the data.
598  $tempArr = explode(',', trim($row[$field]));
599  foreach ($tempArr as $file) {
600  $file = trim($file);
601  if ($file == $filename) {
602  $theRecordList[] = ['table' => $table, 'uid' => $row['uid'], 'field' => $field, 'pid' => $row['pid']];
603  }
604  }
605  }
606  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
607  }
608  return $theRecordList;
609  }
610 }
genTree_records($theID, $_='', $table='', $versions=false)
static getCommonSelectFields($table, $prefix='', $fields=[])
genTree($theID, $depthData='', $versions=false)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
$uid
Definition: server.php:38
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static deleteClause($table, $tableAlias='')