[TASK] Update php-cs-fixer to 2.5.0
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Integrity / DatabaseIntegrityCheck.php
1 <?php
2 namespace TYPO3\CMS\Core\Integrity;
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 Doctrine\DBAL\Types\Type;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
22 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23 use TYPO3\CMS\Core\Database\RelationHandler;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * This class holds functions used by the TYPO3 backend to check the integrity of the database (The DBint module, 'lowlevel' extension)
28 *
29 * Depends on \TYPO3\CMS\Core\Database\RelationHandler
30 *
31 * @todo Need to really extend this class when the DataHandler library has been updated and the whole API is better defined. There are some known bugs in this library. Further it would be nice with a facility to not only analyze but also clean up!
32 * @see \TYPO3\CMS\Lowlevel\View\DatabaseIntegrityView::func_relations(), \TYPO3\CMS\Lowlevel\View\DatabaseIntegrityView::func_records()
33 */
34 class DatabaseIntegrityCheck
35 {
36 /**
37 * @var bool If set, genTree() includes deleted pages. This is default.
38 */
39 public $genTree_includeDeleted = true;
40
41 /**
42 * @var bool If set, genTree() includes versionized pages/records. This is default.
43 */
44 public $genTree_includeVersions = true;
45
46 /**
47 * @var bool If set, genTree() includes records from pages.
48 */
49 public $genTree_includeRecords = false;
50
51 /**
52 * @var array Will hold id/rec pairs from genTree()
53 */
54 public $page_idArray = [];
55
56 /**
57 * @var array
58 */
59 public $rec_idArray = [];
60
61 /**
62 * @var array
63 */
64 public $checkFileRefs = [];
65
66 /**
67 * @var array From the select-fields
68 */
69 public $checkSelectDBRefs = [];
70
71 /**
72 * @var array From the group-fields
73 */
74 public $checkGroupDBRefs = [];
75
76 /**
77 * @var array Statistics
78 */
79 public $recStats = [
80 'allValid' => [],
81 'published_versions' => [],
82 'deleted' => []
83 ];
84
85 /**
86 * @var array
87 */
88 public $lRecords = [];
89
90 /**
91 * @var string
92 */
93 public $lostPagesList = '';
94
95 /**
96 * Generates a list of Page-uid's that corresponds to the tables in the tree.
97 * This list should ideally include all records in the pages-table.
98 *
99 * @param int $theID a pid (page-record id) from which to start making the tree
100 * @param string $depthData HTML-code (image-tags) used when this function calls itself recursively.
101 * @param bool $versions Internal variable, don't set from outside!
102 */
103 public function genTree($theID, $depthData = '', $versions = false)
104 {
105 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
106 $queryBuilder->getRestrictions()->removeAll();
107 if (!$this->genTree_includeDeleted) {
108 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
109 }
110 $queryBuilder->select('uid', 'title', 'doktype', 'deleted', 'hidden')
111 ->from('pages')
112 ->orderBy('sorting');
113 if ($versions) {
114 $queryBuilder->addSelect('t3ver_wsid', 't3ver_id', 't3ver_count');
115 $queryBuilder->where(
116 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
117 $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
118 );
119 } else {
120 $queryBuilder->where(
121 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
122 );
123 }
124 $result = $queryBuilder->execute();
125 // Traverse the records selected
126 while ($row = $result->fetch()) {
127 $newID = $row['uid'];
128 // Register various data for this item:
129 $this->page_idArray[$newID] = $row;
130 $this->recStats['all_valid']['pages'][$newID] = $newID;
131 if ($row['deleted']) {
132 $this->recStats['deleted']['pages'][$newID] = $newID;
133 }
134 if ($versions && $row['t3ver_count'] >= 1) {
135 $this->recStats['published_versions']['pages'][$newID] = $newID;
136 }
137 if ($row['deleted']) {
138 $this->recStats['deleted']++;
139 }
140 if ($row['hidden']) {
141 $this->recStats['hidden']++;
142 }
143 $this->recStats['doktype'][$row['doktype']]++;
144 // If all records should be shown, do so:
145 if ($this->genTree_includeRecords) {
146 foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
147 if ($tableName !== 'pages') {
148 $this->genTree_records($newID, '', $tableName);
149 }
150 }
151 }
152 // Add sub pages:
153 $this->genTree($newID);
154 // If versions are included in the tree, add those now:
155 if ($this->genTree_includeVersions) {
156 $this->genTree($newID, '', true);
157 }
158 }
159 }
160
161 /**
162 * @param int $theID a pid (page-record id) from which to start making the tree
163 * @param string $_ Unused parameter
164 * @param string $table Table to get the records from
165 * @param bool $versions Internal variable, don't set from outside!
166 */
167 public function genTree_records($theID, $_ = '', $table = '', $versions = false)
168 {
169 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
170 $queryBuilder->getRestrictions()->removeAll();
171 if (!$this->genTree_includeDeleted) {
172 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
173 }
174 $queryBuilder
175 ->select(...explode(',', BackendUtility::getCommonSelectFields($table)))
176 ->from($table);
177
178 // Select all records from table pointing to this page
179 if ($versions) {
180 $queryBuilder->where(
181 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
182 $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
183 );
184 } else {
185 $queryBuilder->where(
186 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
187 );
188 }
189 $queryResult = $queryBuilder->execute();
190 // Traverse selected
191 while ($row = $queryResult->fetch()) {
192 $newID = $row['uid'];
193 // Register various data for this item:
194 $this->rec_idArray[$table][$newID] = $row;
195 $this->recStats['all_valid'][$table][$newID] = $newID;
196 if ($row['deleted']) {
197 $this->recStats['deleted'][$table][$newID] = $newID;
198 }
199 if ($versions && $row['t3ver_count'] >= 1 && $row['t3ver_wsid'] == 0) {
200 $this->recStats['published_versions'][$table][$newID] = $newID;
201 }
202 // Select all versions of this record:
203 if ($this->genTree_includeVersions && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
204 $this->genTree_records($newID, '', $table, true);
205 }
206 }
207 }
208
209 /**
210 * Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
211 *
212 * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
213 */
214 public function lostRecords($pid_list)
215 {
216 $this->lostPagesList = '';
217 $pageIds = GeneralUtility::intExplode(',', $pid_list);
218 if (is_array($pageIds)) {
219 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
220 $pageIdsForTable = $pageIds;
221 // Remove preceding "-1," for non-versioned tables
222 if (!BackendUtility::isTableWorkspaceEnabled($table)) {
223 $pageIdsForTable = array_combine($pageIdsForTable, $pageIdsForTable);
224 unset($pageIdsForTable[-1]);
225 }
226 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
227 $queryBuilder->getRestrictions()->removeAll();
228 $selectFields = ['uid', 'pid'];
229 if (!empty($GLOBALS['TCA'][$table]['ctrl']['label'])) {
230 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['label'];
231 }
232 $queryResult = $queryBuilder->select(...$selectFields)
233 ->from($table)
234 ->where(
235 $queryBuilder->expr()->notIn(
236 'pid',
237 $queryBuilder->createNamedParameter($pageIdsForTable, Connection::PARAM_INT_ARRAY)
238 )
239 )
240 ->execute();
241 $lostIdList = [];
242 while ($row = $queryResult->fetch()) {
243 $this->lRecords[$table][$row['uid']] = [
244 'uid' => $row['uid'],
245 'pid' => $row['pid'],
246 'title' => strip_tags(BackendUtility::getRecordTitle($table, $row))
247 ];
248 $lostIdList[] = $row['uid'];
249 }
250 if ($table === 'pages') {
251 $this->lostPagesList = implode(',', $lostIdList);
252 }
253 }
254 }
255 }
256
257 /**
258 * Fixes lost record from $table with uid $uid by setting the PID to zero.
259 * If there is a disabled column for the record that will be set as well.
260 *
261 * @param string $table Database tablename
262 * @param int $uid The uid of the record which will have the PID value set to 0 (zero)
263 * @return bool TRUE if done.
264 */
265 public function fixLostRecord($table, $uid)
266 {
267 if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords[$table][$uid]) && $GLOBALS['BE_USER']->user['admin']) {
268 $updateFields = [
269 'pid' => 0
270 ];
271 // If possible a lost record restored is hidden as default
272 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
273 $updateFields[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] = 1;
274 }
275 GeneralUtility::makeInstance(ConnectionPool::class)
276 ->getConnectionForTable($table)
277 ->update($table, $updateFields, ['uid' => (int)$uid]);
278 return true;
279 }
280 return false;
281 }
282
283 /**
284 * Counts records from $GLOBALS['TCA']-tables that ARE attached to an existing page.
285 *
286 * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
287 * @return array an array with the number of records from all $GLOBALS['TCA']-tables that are attached to a PID in the pid-list.
288 */
289 public function countRecords($pid_list)
290 {
291 $list = [];
292 $list_n = [];
293 $pageIds = GeneralUtility::intExplode(',', $pid_list);
294 if (!empty($pageIds)) {
295 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
296 $pageIdsForTable = $pageIds;
297 // Remove preceding "-1," for non-versioned tables
298 if (!BackendUtility::isTableWorkspaceEnabled($table)) {
299 $pageIdsForTable = array_combine($pageIdsForTable, $pageIdsForTable);
300 unset($pageIdsForTable[-1]);
301 }
302 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
303 $queryBuilder->getRestrictions()->removeAll();
304 $count = $queryBuilder->count('uid')
305 ->from($table)
306 ->where(
307 $queryBuilder->expr()->in(
308 'pid',
309 $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
310 )
311 )
312 ->execute()
313 ->fetchColumn(0);
314 if ($count) {
315 $list[$table] = $count;
316 }
317
318 // same query excluding all deleted records
319 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
320 $queryBuilder->getRestrictions()
321 ->removeAll()
322 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
323 $count = $queryBuilder->count('uid')
324 ->from($table)
325 ->where(
326 $queryBuilder->expr()->in(
327 'pid',
328 $queryBuilder->createNamedParameter($pageIdsForTable, Connection::PARAM_INT_ARRAY)
329 )
330 )
331 ->execute()
332 ->fetchColumn(0);
333 if ($count) {
334 $list_n[$table] = $count;
335 }
336 }
337 }
338 return ['all' => $list, 'non_deleted' => $list_n];
339 }
340
341 /**
342 * Finding relations in database based on type 'group' (files or database-uid's in a list)
343 *
344 * @param string $mode $mode = file, $mode = db, $mode = '' (all...)
345 * @return array An array with all fields listed that somehow are references to other records (foreign-keys) or files
346 */
347 public function getGroupFields($mode)
348 {
349 $result = [];
350 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
351 $cols = $GLOBALS['TCA'][$table]['columns'];
352 foreach ($cols as $field => $config) {
353 if ($config['config']['type'] === 'group') {
354 if ((!$mode || $mode === 'file') && $config['config']['internal_type'] === 'file' || (!$mode || $mode === 'db') && $config['config']['internal_type'] === 'db') {
355 $result[$table][] = $field;
356 }
357 }
358 if ((!$mode || $mode === 'db') && $config['config']['type'] === 'select' && $config['config']['foreign_table']) {
359 $result[$table][] = $field;
360 }
361 }
362 if ($result[$table]) {
363 $result[$table] = implode(',', $result[$table]);
364 }
365 }
366 return $result;
367 }
368
369 /**
370 * Finds all fields that hold filenames from uploadfolder
371 *
372 * @param string $uploadfolder Path to uploadfolder
373 * @return array An array with all fields listed that have references to files in the $uploadfolder
374 */
375 public function getFileFields($uploadfolder)
376 {
377 $result = [];
378 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
379 $cols = $GLOBALS['TCA'][$table]['columns'];
380 foreach ($cols as $field => $config) {
381 if ($config['config']['type'] === 'group' && $config['config']['internal_type'] === 'file' && $config['config']['uploadfolder'] == $uploadfolder) {
382 $result[] = [$table, $field];
383 }
384 }
385 }
386 return $result;
387 }
388
389 /**
390 * Returns an array with arrays of table/field pairs which are allowed to hold references to the input table name - according to $GLOBALS['TCA']
391 *
392 * @param string $theSearchTable Table name
393 * @return array
394 */
395 public function getDBFields($theSearchTable)
396 {
397 $result = [];
398 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
399 $cols = $GLOBALS['TCA'][$table]['columns'];
400 foreach ($cols as $field => $config) {
401 if ($config['config']['type'] === 'group' && $config['config']['internal_type'] === 'db') {
402 if (trim($config['config']['allowed']) === '*' || strstr($config['config']['allowed'], $theSearchTable)) {
403 $result[] = [$table, $field];
404 }
405 } elseif ($config['config']['type'] === 'select' && $config['config']['foreign_table'] == $theSearchTable) {
406 $result[] = [$table, $field];
407 }
408 }
409 }
410 return $result;
411 }
412
413 /**
414 * This selects non-empty-records from the tables/fields in the fkey_array generated by getGroupFields()
415 *
416 * @param array $fkey_arrays Array with tables/fields generated by getGroupFields()
417 * @see getGroupFields()
418 */
419 public function selectNonEmptyRecordsWithFkeys($fkey_arrays)
420 {
421 if (is_array($fkey_arrays)) {
422 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
423 foreach ($fkey_arrays as $table => $field_list) {
424 if ($GLOBALS['TCA'][$table] && trim($field_list)) {
425 $connection = $connectionPool->getConnectionForTable($table);
426 $schemaManager = $connection->getSchemaManager();
427 $tableColumns = $schemaManager->listTableColumns($table);
428
429 $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
430 $queryBuilder->getRestrictions()->removeAll();
431
432 $fields = GeneralUtility::trimExplode(',', $field_list, true);
433
434 $queryBuilder->select('uid')
435 ->from($table);
436 $whereClause = [];
437
438 foreach ($fields as $fieldName) {
439 // The array index of $tableColumns is the lowercased column name!
440 // It is quoted for keywords
441 $column = $tableColumns[strtolower($fieldName)]
442 ?? $tableColumns[$connection->quoteIdentifier(strtolower($fieldName))];
443 $fieldType = $column->getType()->getName();
444 if (in_array(
445 $fieldType,
446 [Type::BIGINT, Type::INTEGER, Type::SMALLINT, Type::DECIMAL, Type::FLOAT],
447 true
448 )) {
449 $whereClause[] = $queryBuilder->expr()->andX(
450 $queryBuilder->expr()->isNotNull($fieldName),
451 $queryBuilder->expr()->neq(
452 $fieldName,
453 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
454 )
455 );
456 } elseif (in_array($fieldType, [Type::STRING, Type::TEXT], true)) {
457 $whereClause[] = $queryBuilder->expr()->andX(
458 $queryBuilder->expr()->isNotNull($fieldName),
459 $queryBuilder->expr()->neq(
460 $fieldName,
461 $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
462 )
463 );
464 } elseif (in_array($fieldType, [Type::BLOB], true)) {
465 $whereClause[] = $queryBuilder->expr()->andX(
466 $queryBuilder->expr()->isNotNull($fieldName),
467 $queryBuilder->expr()
468 ->comparison(
469 $queryBuilder->expr()->length($fieldName),
470 ExpressionBuilder::GT,
471 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
472 )
473 );
474 }
475 }
476 $queryResult = $queryBuilder->orWhere(...$whereClause)->execute();
477
478 while ($row = $queryResult->fetch()) {
479 foreach ($fields as $field) {
480 if (trim($row[$field])) {
481 $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
482 if ($fieldConf['type'] === 'group') {
483 if ($fieldConf['internal_type'] === 'file') {
484 // Files...
485 if ($fieldConf['MM']) {
486 $tempArr = [];
487 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
488 $dbAnalysis->start('', 'files', $fieldConf['MM'], $row['uid']);
489 foreach ($dbAnalysis->itemArray as $somekey => $someval) {
490 if ($someval['id']) {
491 $tempArr[] = $someval['id'];
492 }
493 }
494 } else {
495 $tempArr = explode(',', trim($row[$field]));
496 }
497 foreach ($tempArr as $file) {
498 $file = trim($file);
499 if ($file) {
500 $this->checkFileRefs[$fieldConf['uploadfolder']][$file] += 1;
501 }
502 }
503 }
504 if ($fieldConf['internal_type'] === 'db') {
505 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
506 $dbAnalysis->start(
507 $row[$field],
508 $fieldConf['allowed'],
509 $fieldConf['MM'],
510 $row['uid'],
511 $table,
512 $fieldConf
513 );
514 foreach ($dbAnalysis->itemArray as $tempArr) {
515 $this->checkGroupDBRefs[$tempArr['table']][$tempArr['id']] += 1;
516 }
517 }
518 }
519 if ($fieldConf['type'] === 'select' && $fieldConf['foreign_table']) {
520 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
521 $dbAnalysis->start(
522 $row[$field],
523 $fieldConf['foreign_table'],
524 $fieldConf['MM'],
525 $row['uid'],
526 $table,
527 $fieldConf
528 );
529 foreach ($dbAnalysis->itemArray as $tempArr) {
530 if ($tempArr['id'] > 0) {
531 $this->checkGroupDBRefs[$fieldConf['foreign_table']][$tempArr['id']] += 1;
532 }
533 }
534 }
535 }
536 }
537 }
538 }
539 }
540 }
541 }
542
543 /**
544 * Depends on selectNonEmpty.... to be executed first!!
545 *
546 * @return array Report over files; keys are "moreReferences", "noReferences", "noFile", "error
547 */
548 public function testFileRefs()
549 {
550 $output = [];
551 // Handle direct references with upload folder setting (workaround)
552 $newCheckFileRefs = [];
553 foreach ($this->checkFileRefs as $folder => $files) {
554 // Only direct references without a folder setting
555 if ($folder !== '') {
556 $newCheckFileRefs[$folder] = $files;
557 continue;
558 }
559 foreach ($files as $file => $references) {
560 // Direct file references have often many references (removes occurrences in the moreReferences section of the result array)
561 if ($references > 1) {
562 $references = 1;
563 }
564 // The directory must be empty (prevents checking of the root directory)
565 $directory = dirname($file);
566 if ($directory !== '') {
567 $newCheckFileRefs[$directory][basename($file)] = $references;
568 }
569 }
570 }
571 $this->checkFileRefs = $newCheckFileRefs;
572 foreach ($this->checkFileRefs as $folder => $fileArr) {
573 $path = PATH_site . $folder;
574 if (@is_dir($path) && @is_readable($path)) {
575 $d = dir($path);
576 while ($entry = $d->read()) {
577 if (@is_file(($path . '/' . $entry))) {
578 if (isset($fileArr[$entry])) {
579 if ($fileArr[$entry] > 1) {
580 $temp = $this->whereIsFileReferenced($folder, $entry);
581 $tempList = '';
582 foreach ($temp as $inf) {
583 $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
584 }
585 $output['moreReferences'][] = [$path, $entry, $fileArr[$entry], $tempList];
586 }
587 unset($fileArr[$entry]);
588 } else {
589 // Contains workaround for direct references
590 if (!strstr($entry, 'index.htm') && !preg_match(('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/'), $folder)) {
591 $output['noReferences'][] = [$path, $entry];
592 }
593 }
594 }
595 }
596 $d->close();
597 $tempCounter = 0;
598 foreach ($fileArr as $file => $value) {
599 // Workaround for direct file references
600 if (preg_match('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/', $folder)) {
601 $file = $folder . '/' . $file;
602 $folder = '';
603 $path = substr(PATH_site, 0, -1);
604 }
605 $temp = $this->whereIsFileReferenced($folder, $file);
606 $tempList = '';
607 foreach ($temp as $inf) {
608 $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
609 }
610 $tempCounter++;
611 $output['noFile'][substr($path, -3) . '_' . substr($file, 0, 3) . '_' . $tempCounter] = [$path, $file, $tempList];
612 }
613 } else {
614 $output['error'][] = [$path];
615 }
616 }
617 return $output;
618 }
619
620 /**
621 * Depends on selectNonEmpty.... to be executed first!!
622 *
623 * @param array $theArray Table with key/value pairs being table names and arrays with uid numbers
624 * @return string HTML Error message
625 */
626 public function testDBRefs($theArray)
627 {
628 $result = '';
629 foreach ($theArray as $table => $dbArr) {
630 if ($GLOBALS['TCA'][$table]) {
631 $ids = array_keys($dbArr);
632 if (!empty($ids)) {
633 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
634 ->getQueryBuilderForTable($table);
635 $queryBuilder->getRestrictions()
636 ->removeAll()
637 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
638 $queryResult = $queryBuilder
639 ->select('uid')
640 ->from($table)
641 ->where(
642 $queryBuilder->expr()->in(
643 'uid',
644 $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
645 )
646 )
647 ->execute();
648 while ($row = $queryResult->fetch()) {
649 if (isset($dbArr[$row['uid']])) {
650 unset($dbArr[$row['uid']]);
651 } else {
652 $result .= 'Strange Error. ...<br />';
653 }
654 }
655 foreach ($dbArr as $theId => $theC) {
656 $result .= 'There are ' . $theC . ' records pointing to this missing or deleted record; [' . $table . '][' . $theId . ']<br />';
657 }
658 }
659 } else {
660 $result .= 'Codeerror. Table is not a table...<br />';
661 }
662 }
663 return $result;
664 }
665
666 /**
667 * Finding all references to record based on table/uid
668 *
669 * @param string $searchTable Table name
670 * @param int $id Uid of database record
671 * @return array Array with other arrays containing information about where references was found
672 */
673 public function whereIsRecordReferenced($searchTable, $id)
674 {
675 // Gets tables / Fields that reference to files
676 $fileFields = $this->getDBFields($searchTable);
677 $theRecordList = [];
678 foreach ($fileFields as $info) {
679 list($table, $field) = $info;
680 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
681 $queryBuilder->getRestrictions()->removeAll();
682 $queryResult = $queryBuilder
683 ->select('uid', 'pid', $GLOBALS['TCA'][$table]['ctrl']['label'], $field)
684 ->from($table)
685 ->where(
686 $queryBuilder->expr()->like(
687 $field,
688 $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($id) . '%')
689 )
690 )
691 ->execute();
692
693 while ($row = $queryResult->fetch()) {
694 // Now this is the field, where the reference COULD come from.
695 // But we're not guaranteed, so we must carefully examine the data.
696 $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
697 $allowedTables = $fieldConf['type'] === 'group' ? $fieldConf['allowed'] : $fieldConf['foreign_table'];
698 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
699 $dbAnalysis->start($row[$field], $allowedTables, $fieldConf['MM'], $row['uid'], $table, $fieldConf);
700 foreach ($dbAnalysis->itemArray as $tempArr) {
701 if ($tempArr['table'] == $searchTable && $tempArr['id'] == $id) {
702 $theRecordList[] = [
703 'table' => $table,
704 'uid' => $row['uid'],
705 'field' => $field,
706 'pid' => $row['pid']
707 ];
708 }
709 }
710 }
711 }
712 return $theRecordList;
713 }
714
715 /**
716 * Finding all references to file based on uploadfolder / filename
717 *
718 * @param string $uploadFolder Upload folder where file is found
719 * @param string $filename Filename to search for
720 * @return array Array with other arrays containing information about where references was found
721 */
722 public function whereIsFileReferenced($uploadFolder, $filename)
723 {
724 // Gets tables / Fields that reference to files
725 $fileFields = $this->getFileFields($uploadFolder);
726 $theRecordList = [];
727 foreach ($fileFields as $info) {
728 list($table, $field) = $info;
729 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
730 $queryBuilder->getRestrictions()->removeAll();
731 $queryResult = $queryBuilder
732 ->select('uid', 'pid', $GLOBALS['TCA'][$table]['ctrl']['label'], $field)
733 ->from($table)
734 ->where(
735 $queryBuilder->expr()->like(
736 $field,
737 $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($filename) . '%')
738 )
739 )
740 ->execute();
741 while ($row = $queryResult->fetch()) {
742 // Now this is the field, where the reference COULD come from.
743 // But we're not guaranteed, so we must carefully examine the data.
744 $tempArr = explode(',', trim($row[$field]));
745 foreach ($tempArr as $file) {
746 $file = trim($file);
747 if ($file == $filename) {
748 $theRecordList[] = [
749 'table' => $table,
750 'uid' => $row['uid'],
751 'field' => $field,
752 'pid' => $row['pid']
753 ];
754 }
755 }
756 }
757 }
758 return $theRecordList;
759 }
760 }