[TASK] Move DatabaseIntegrityCheck to EXT:lowlevel 65/57765/4
authorBenni Mack <benni@typo3.org>
Wed, 1 Aug 2018 20:16:01 +0000 (22:16 +0200)
committerWouter Wolters <typo3@wouterwolters.nl>
Thu, 2 Aug 2018 17:27:38 +0000 (19:27 +0200)
The class DatabaseIntegrityCheck is solely needed in
EXT:lowlevel, and is moved to the system extension
out of EXT:core.

Resolves: #85727
Releases: master
Change-Id: I86e3b359f4692746aa87ea153edf0b18c89cea30
Reviewed-on: https://review.typo3.org/57765
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
typo3/sysext/core/Classes/Integrity/DatabaseIntegrityCheck.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-85727-DatabaseIntegrityCheckMovedToEXTlowlevel.rst [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php [new file with mode: 0644]

index 1ca8638..12667c1 100644 (file)
@@ -100,6 +100,14 @@ class DatabaseIntegrityCheck
     public $lostPagesList = '';
 
     /**
+     * DatabaseIntegrityCheck constructor.
+     */
+    public function __construct()
+    {
+        trigger_error('TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck will be removed in TYPO3 v10.0, use TYPO3\CMS\Lowlevel\Integrity\DatabaseIntegrityCheck instead.', E_USER_DEPRECATED);
+    }
+
+    /**
      * @return array
      */
     public function getPageTranslatedPageIDArray(): array
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85727-DatabaseIntegrityCheckMovedToEXTlowlevel.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85727-DatabaseIntegrityCheckMovedToEXTlowlevel.rst
new file mode 100644 (file)
index 0000000..ee3d520
--- /dev/null
@@ -0,0 +1,36 @@
+.. include:: ../../Includes.txt
+
+==================================================================
+Deprecation: #85727 - DatabaseIntegrityCheck moved to EXT:lowlevel
+==================================================================
+
+See :issue:`85727`
+
+Description
+===========
+
+The PHP class :php:`TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck` has been moved from the system
+extension `core` to `lowlevel`. The PHP class has been renamed to
+:php:`TYPO3\CMS\Lowlevel\Integrity\DatabaseIntegrityCheck`.
+
+
+Impact
+======
+
+Calling the old class name will trigger a deprecation message.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installation where this PHP class is in use within a TYPO3 extension.
+
+
+Migration
+=========
+
+Ensure that the system extension `lowlevel` is installed, and the caller code uses the new class name.
+
+For TYPO3 v9, the old class is kept in place and will be removed in v10.
+
+.. index:: FullyScanned, ext:lowlevel
index 332dd7e..c5c99cc 100644 (file)
@@ -704,4 +704,9 @@ return [
             'Deprecation-85707-LoginFramesetController.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck' => [
+        'restFiles' => [
+            'Deprecation-85727-DatabaseIntegrityCheckMovedToEXTlowlevel.rst',
+        ],
+    ],
 ];
index 7a06bfa..f3dfdb3 100644 (file)
@@ -24,13 +24,13 @@ use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Integrity\DatabaseIntegrityCheck;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Lowlevel\Integrity\DatabaseIntegrityCheck;
 
 /**
  * Script class for the DB int module
@@ -375,7 +375,7 @@ class DatabaseIntegrityController
         $pageStatistic = [
             'total_pages' => [
                 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
-                'count' => count($admin->page_idArray)
+                'count' => count($admin->getPageIdArray())
             ],
             'translated_pages' => [
                 'icon' => $this->iconFactory->getIconForRecord('pages', [], Icon::SIZE_SMALL)->render(),
@@ -383,11 +383,11 @@ class DatabaseIntegrityController
             ],
             'hidden_pages' => [
                 'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], Icon::SIZE_SMALL)->render(),
-                'count' => $admin->recStats['hidden']
+                'count' => $admin->getRecStats()['hidden'] ?? 0
             ],
             'deleted_pages' => [
                 'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], Icon::SIZE_SMALL)->render(),
-                'count' => isset($admin->recStats['deleted']['pages']) ? count($admin->recStats['deleted']['pages']) : 0
+                'count' => isset($admin->getRecStats()['deleted']['pages']) ? count($admin->getRecStats()['deleted']['pages']) : 0
             ]
         ];
 
@@ -402,20 +402,20 @@ class DatabaseIntegrityController
                     $doktypes[] = [
                         'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup[1]], Icon::SIZE_SMALL)->render(),
                         'title' => $lang->sL($setup[0]) . ' (' . $setup[1] . ')',
-                        'count' => (int)$admin->recStats['doktype'][$setup[1]]
+                        'count' => (int)($admin->getRecStats()['doktype'][$setup[1]] ?? 0)
                     ];
                 }
             }
         }
 
         // Tables and lost records
-        $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
+        $id_list = '-1,0,' . implode(',', array_keys($admin->getPageIdArray()));
         $id_list = rtrim($id_list, ',');
         $admin->lostRecords($id_list);
         if ($admin->fixLostRecord(GeneralUtility::_GET('fixLostRecords_table'), GeneralUtility::_GET('fixLostRecords_uid'))) {
             $admin = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
             $admin->genTree(0);
-            $id_list = '-1,0,' . implode(',', array_keys($admin->page_idArray));
+            $id_list = '-1,0,' . implode(',', array_keys($admin->getPageIdArray()));
             $id_list = rtrim($id_list, ',');
             $admin->lostRecords($id_list);
         }
@@ -426,10 +426,10 @@ class DatabaseIntegrityController
                 if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
                     continue;
                 }
-                if ($t === 'pages' && $admin->lostPagesList !== '') {
-                    $lostRecordCount = count(explode(',', $admin->lostPagesList));
+                if ($t === 'pages' && $admin->getLostPagesList() !== '') {
+                    $lostRecordCount = count(explode(',', $admin->getLostPagesList()));
                 } else {
-                    $lostRecordCount = isset($admin->lRecords[$t]) ? count($admin->lRecords[$t]) : 0;
+                    $lostRecordCount = isset($admin->getLRecords()[$t]) ? count($admin->getLRecords()[$t]) : 0;
                 }
                 if ($countArr['all'][$t]) {
                     $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
@@ -439,9 +439,9 @@ class DatabaseIntegrityController
                 $lr = '';
                 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
                 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
-                if (is_array($admin->lRecords[$t])) {
-                    foreach ($admin->lRecords[$t] as $data) {
-                        if (!GeneralUtility::inList($admin->lostPagesList, $data['pid'])) {
+                if (is_array($admin->getLRecords()[$t])) {
+                    foreach ($admin->getLRecords()[$t] as $data) {
+                        if (!GeneralUtility::inList($admin->getLostPagesList(), $data['pid'])) {
                             $lr .= '<div class="record"><a href="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('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>';
                         } else {
                             $lr .= '<div class="record-noicon">uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
@@ -479,8 +479,8 @@ class DatabaseIntegrityController
         }
         $this->view->assignMultiple([
             'files' =>  $fileTest,
-            'select_db' => $admin->testDBRefs($admin->checkSelectDBRefs),
-            'group_db' => $admin->testDBRefs($admin->checkGroupDBRefs)
+            'select_db' => $admin->testDBRefs($admin->getCheckSelectDBRefs()),
+            'group_db' => $admin->testDBRefs($admin->getCheckGroupDBRefs())
         ]);
     }
 
diff --git a/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php b/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php
new file mode 100644 (file)
index 0000000..301e72c
--- /dev/null
@@ -0,0 +1,831 @@
+<?php
+namespace TYPO3\CMS\Lowlevel\Integrity;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Doctrine\DBAL\Types\Type;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+/**
+ * This class holds functions used by the TYPO3 backend to check the integrity
+ * of the database (The DBint module, 'lowlevel' extension)
+ *
+ * Depends on \TYPO3\CMS\Core\Database\RelationHandler
+ *
+ * @TODO: Need to really extend this class when the DataHandler library has been
+ * @TODO: updated and the whole API is better defined. There are some known bugs
+ * @TODO: in this library. Further it would be nice with a facility to not only
+ * @TODO: analyze but also clean up!
+ * @see \TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController::func_relations(), \TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController::func_records()
+ */
+class DatabaseIntegrityCheck
+{
+    /**
+     * @var bool If set, genTree() includes deleted pages. This is default.
+     */
+    protected $genTreeIncludeDeleted = true;
+
+    /**
+     * @var bool If set, genTree() includes versionized pages/records. This is default.
+     */
+    protected $genTreeIncludeVersions = true;
+
+    /**
+     * @var bool If set, genTree() includes records from pages.
+     */
+    protected $genTreeIncludeRecords = false;
+
+    /**
+     * @var array Will hold id/rec pairs from genTree()
+     */
+    protected $pageIdArray = [];
+
+    /**
+     * @var array Will hold id/rec pairs from genTree() that are not default language
+     */
+    protected $pageTranslatedPageIDArray = [];
+
+    /**
+     * @var array
+     */
+    protected $recIdArray = [];
+
+    /**
+     * @var array
+     */
+    protected $checkFileRefs = [];
+
+    /**
+     * @var array From the select-fields
+     */
+    protected $checkSelectDBRefs = [];
+
+    /**
+     * @var array From the group-fields
+     */
+    protected $checkGroupDBRefs = [];
+
+    /**
+     * @var array Statistics
+     */
+    protected $recStats = [
+        'allValid' => [],
+        'published_versions' => [],
+        'deleted' => []
+    ];
+
+    /**
+     * @var array
+     */
+    protected $lRecords = [];
+
+    /**
+     * @var string
+     */
+    protected $lostPagesList = '';
+
+    /**
+     * @return array
+     */
+    public function getPageTranslatedPageIDArray(): array
+    {
+        return $this->pageTranslatedPageIDArray;
+    }
+
+    /**
+     * Generates a list of Page-uid's that corresponds to the tables in the tree.
+     * This list should ideally include all records in the pages-table.
+     *
+     * @param int $theID a pid (page-record id) from which to start making the tree
+     * @param string $depthData HTML-code (image-tags) used when this function calls itself recursively.
+     * @param bool $versions Internal variable, don't set from outside!
+     */
+    public function genTree($theID, $depthData = '', $versions = false)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll();
+        if (!$this->genTreeIncludeDeleted) {
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        }
+        $queryBuilder->select('uid', 'title', 'doktype', 'deleted', 'hidden', 'sys_language_uid')
+            ->from('pages')
+            ->orderBy('sorting');
+        if ($versions) {
+            $queryBuilder->addSelect('t3ver_wsid', 't3ver_id', 't3ver_count');
+            $queryBuilder->where(
+                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
+                $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
+            );
+        } else {
+            $queryBuilder->where(
+                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
+            );
+        }
+        $result = $queryBuilder->execute();
+        // Traverse the records selected
+        while ($row = $result->fetch()) {
+            $newID = $row['uid'];
+            // Register various data for this item:
+            if ($row['sys_language_uid'] === 0) {
+                $this->pageIdArray[$newID] = $row;
+            } else {
+                $this->pageTranslatedPageIDArray[$newID] = $row;
+            }
+            $this->recStats['all_valid']['pages'][$newID] = $newID;
+            if ($row['deleted']) {
+                $this->recStats['deleted']['pages'][$newID] = $newID;
+            }
+            if ($versions && $row['t3ver_count'] >= 1) {
+                $this->recStats['published_versions']['pages'][$newID] = $newID;
+            }
+            if ($row['deleted']) {
+                $this->recStats['deleted']++;
+            }
+            if ($row['hidden']) {
+                $this->recStats['hidden']++;
+            }
+            $this->recStats['doktype'][$row['doktype']]++;
+            // If all records should be shown, do so:
+            if ($this->genTreeIncludeRecords) {
+                foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
+                    if ($tableName !== 'pages') {
+                        $this->genTree_records($newID, '', $tableName);
+                    }
+                }
+            }
+            // Add sub pages:
+            $this->genTree($newID);
+            // If versions are included in the tree, add those now:
+            if ($this->genTreeIncludeVersions) {
+                $this->genTree($newID, '', true);
+            }
+        }
+    }
+
+    /**
+     * @param int $theID a pid (page-record id) from which to start making the tree
+     * @param string $_ Unused parameter
+     * @param string $table Table to get the records from
+     * @param bool $versions Internal variable, don't set from outside!
+     */
+    public function genTree_records($theID, $_ = '', $table = '', $versions = false): void
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()->removeAll();
+        if (!$this->genTreeIncludeDeleted) {
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        }
+        $queryBuilder
+            ->select(...explode(',', BackendUtility::getCommonSelectFields($table)))
+            ->from($table);
+
+        // Select all records from table pointing to this page
+        if ($versions) {
+            $queryBuilder->where(
+                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
+                $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
+            );
+        } else {
+            $queryBuilder->where(
+                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($theID, \PDO::PARAM_INT))
+            );
+        }
+        $queryResult = $queryBuilder->execute();
+        // Traverse selected
+        while ($row = $queryResult->fetch()) {
+            $newID = $row['uid'];
+            // Register various data for this item:
+            $this->recIdArray[$table][$newID] = $row;
+            $this->recStats['all_valid'][$table][$newID] = $newID;
+            if ($row['deleted']) {
+                $this->recStats['deleted'][$table][$newID] = $newID;
+            }
+            if ($versions && $row['t3ver_count'] >= 1 && $row['t3ver_wsid'] == 0) {
+                $this->recStats['published_versions'][$table][$newID] = $newID;
+            }
+            // Select all versions of this record:
+            if ($this->genTreeIncludeVersions && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                $this->genTree_records($newID, '', $table, true);
+            }
+        }
+    }
+
+    /**
+     * Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
+     *
+     * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
+     */
+    public function lostRecords($pid_list): void
+    {
+        $this->lostPagesList = '';
+        $pageIds = GeneralUtility::intExplode(',', $pid_list);
+        if (is_array($pageIds)) {
+            foreach ($GLOBALS['TCA'] as $table => $tableConf) {
+                $pageIdsForTable = $pageIds;
+                // Remove preceding "-1," for non-versioned tables
+                if (!BackendUtility::isTableWorkspaceEnabled($table)) {
+                    $pageIdsForTable = array_combine($pageIdsForTable, $pageIdsForTable);
+                    unset($pageIdsForTable[-1]);
+                }
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+                $queryBuilder->getRestrictions()->removeAll();
+                $selectFields = ['uid', 'pid'];
+                if (!empty($GLOBALS['TCA'][$table]['ctrl']['label'])) {
+                    $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['label'];
+                }
+                $queryResult = $queryBuilder->select(...$selectFields)
+                    ->from($table)
+                    ->where(
+                        $queryBuilder->expr()->notIn(
+                            'pid',
+                            $queryBuilder->createNamedParameter($pageIdsForTable, Connection::PARAM_INT_ARRAY)
+                        )
+                    )
+                    ->execute();
+                $lostIdList = [];
+                while ($row = $queryResult->fetch()) {
+                    $this->lRecords[$table][$row['uid']] = [
+                        'uid' => $row['uid'],
+                        'pid' => $row['pid'],
+                        'title' => strip_tags(BackendUtility::getRecordTitle($table, $row))
+                    ];
+                    $lostIdList[] = $row['uid'];
+                }
+                if ($table === 'pages') {
+                    $this->lostPagesList = implode(',', $lostIdList);
+                }
+            }
+        }
+    }
+
+    /**
+     * Fixes lost record from $table with uid $uid by setting the PID to zero.
+     * If there is a disabled column for the record that will be set as well.
+     *
+     * @param string $table Database tablename
+     * @param int $uid The uid of the record which will have the PID value set to 0 (zero)
+     * @return bool TRUE if done.
+     */
+    public function fixLostRecord($table, $uid): bool
+    {
+        if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords[$table][$uid]) && $GLOBALS['BE_USER']->isAdmin()) {
+            $updateFields = [
+                'pid' => 0
+            ];
+            // If possible a lost record restored is hidden as default
+            if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
+                $updateFields[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] = 1;
+            }
+            GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getConnectionForTable($table)
+                ->update($table, $updateFields, ['uid' => (int)$uid]);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Counts records from $GLOBALS['TCA']-tables that ARE attached to an existing page.
+     *
+     * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
+     * @return array an array with the number of records from all $GLOBALS['TCA']-tables that are attached to a PID in the pid-list.
+     */
+    public function countRecords($pid_list): array
+    {
+        $list = [];
+        $list_n = [];
+        $pageIds = GeneralUtility::intExplode(',', $pid_list);
+        if (!empty($pageIds)) {
+            foreach ($GLOBALS['TCA'] as $table => $tableConf) {
+                $pageIdsForTable = $pageIds;
+                // Remove preceding "-1," for non-versioned tables
+                if (!BackendUtility::isTableWorkspaceEnabled($table)) {
+                    $pageIdsForTable = array_combine($pageIdsForTable, $pageIdsForTable);
+                    unset($pageIdsForTable[-1]);
+                }
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+                $queryBuilder->getRestrictions()->removeAll();
+                $count = $queryBuilder->count('uid')
+                    ->from($table)
+                    ->where(
+                        $queryBuilder->expr()->in(
+                            'pid',
+                            $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
+                        )
+                    )
+                    ->execute()
+                    ->fetchColumn(0);
+                if ($count) {
+                    $list[$table] = $count;
+                }
+
+                // same query excluding all deleted records
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+                $count = $queryBuilder->count('uid')
+                    ->from($table)
+                    ->where(
+                        $queryBuilder->expr()->in(
+                            'pid',
+                            $queryBuilder->createNamedParameter($pageIdsForTable, Connection::PARAM_INT_ARRAY)
+                        )
+                    )
+                    ->execute()
+                    ->fetchColumn(0);
+                if ($count) {
+                    $list_n[$table] = $count;
+                }
+            }
+        }
+        return ['all' => $list, 'non_deleted' => $list_n];
+    }
+
+    /**
+     * Finding relations in database based on type 'group' (files or database-uid's in a list)
+     *
+     * @param string $mode $mode = file, $mode = db, $mode = '' (all...)
+     * @return array An array with all fields listed that somehow are references to other records (foreign-keys) or files
+     */
+    public function getGroupFields($mode): array
+    {
+        $result = [];
+        foreach ($GLOBALS['TCA'] as $table => $tableConf) {
+            $cols = $GLOBALS['TCA'][$table]['columns'];
+            foreach ($cols as $field => $config) {
+                if ($config['config']['type'] === 'group') {
+                    if ((!$mode || $mode === 'file') && $config['config']['internal_type'] === 'file' || (!$mode || $mode === 'db') && $config['config']['internal_type'] === 'db') {
+                        $result[$table][] = $field;
+                    }
+                }
+                if ((!$mode || $mode === 'db') && $config['config']['type'] === 'select' && $config['config']['foreign_table']) {
+                    $result[$table][] = $field;
+                }
+            }
+            if ($result[$table]) {
+                $result[$table] = implode(',', $result[$table]);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Finds all fields that hold filenames from uploadfolder
+     *
+     * @param string $uploadfolder Path to uploadfolder
+     * @return array An array with all fields listed that have references to files in the $uploadfolder
+     */
+    public function getFileFields($uploadfolder): array
+    {
+        $result = [];
+        foreach ($GLOBALS['TCA'] as $table => $tableConf) {
+            $cols = $GLOBALS['TCA'][$table]['columns'];
+            foreach ($cols as $field => $config) {
+                if ($config['config']['type'] === 'group' && $config['config']['internal_type'] === 'file' && $config['config']['uploadfolder'] == $uploadfolder) {
+                    $result[] = [$table, $field];
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Returns an array with arrays of table/field pairs which are allowed to hold references to the input table name - according to $GLOBALS['TCA']
+     *
+     * @param string $theSearchTable Table name
+     * @return array
+     */
+    public function getDBFields($theSearchTable): array
+    {
+        $result = [];
+        foreach ($GLOBALS['TCA'] as $table => $tableConf) {
+            $cols = $GLOBALS['TCA'][$table]['columns'];
+            foreach ($cols as $field => $config) {
+                if ($config['config']['type'] === 'group' && $config['config']['internal_type'] === 'db') {
+                    if (trim($config['config']['allowed']) === '*' || strstr($config['config']['allowed'], $theSearchTable)) {
+                        $result[] = [$table, $field];
+                    }
+                } elseif ($config['config']['type'] === 'select' && $config['config']['foreign_table'] == $theSearchTable) {
+                    $result[] = [$table, $field];
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * This selects non-empty-records from the tables/fields in the fkey_array generated by getGroupFields()
+     *
+     * @param array $fkey_arrays Array with tables/fields generated by getGroupFields()
+     * @see getGroupFields()
+     */
+    public function selectNonEmptyRecordsWithFkeys($fkey_arrays): void
+    {
+        if (is_array($fkey_arrays)) {
+            $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+            foreach ($fkey_arrays as $table => $field_list) {
+                if ($GLOBALS['TCA'][$table] && trim($field_list)) {
+                    $connection = $connectionPool->getConnectionForTable($table);
+                    $schemaManager = $connection->getSchemaManager();
+                    $tableColumns = $schemaManager->listTableColumns($table);
+
+                    $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
+                    $queryBuilder->getRestrictions()->removeAll();
+
+                    $fields = GeneralUtility::trimExplode(',', $field_list, true);
+
+                    $queryBuilder->select('uid')
+                        ->from($table);
+                    $whereClause = [];
+
+                    foreach ($fields as $fieldName) {
+                        // The array index of $tableColumns is the lowercased column name!
+                        // It is quoted for keywords
+                        $column = $tableColumns[strtolower($fieldName)]
+                            ?? $tableColumns[$connection->quoteIdentifier(strtolower($fieldName))];
+                        $fieldType = $column->getType()->getName();
+                        if (in_array(
+                            $fieldType,
+                            [Type::BIGINT, Type::INTEGER, Type::SMALLINT, Type::DECIMAL, Type::FLOAT],
+                            true
+                        )) {
+                            $whereClause[] = $queryBuilder->expr()->andX(
+                                $queryBuilder->expr()->isNotNull($fieldName),
+                                $queryBuilder->expr()->neq(
+                                    $fieldName,
+                                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                                )
+                            );
+                        } elseif (in_array($fieldType, [Type::STRING, Type::TEXT], true)) {
+                            $whereClause[] = $queryBuilder->expr()->andX(
+                                $queryBuilder->expr()->isNotNull($fieldName),
+                                $queryBuilder->expr()->neq(
+                                    $fieldName,
+                                    $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
+                                )
+                            );
+                        } elseif ($fieldType === Type::BLOB) {
+                            $whereClause[] = $queryBuilder->expr()->andX(
+                                $queryBuilder->expr()->isNotNull($fieldName),
+                                $queryBuilder->expr()
+                                    ->comparison(
+                                        $queryBuilder->expr()->length($fieldName),
+                                        ExpressionBuilder::GT,
+                                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                                    )
+                            );
+                        }
+                    }
+                    $queryResult = $queryBuilder->orWhere(...$whereClause)->execute();
+
+                    while ($row = $queryResult->fetch()) {
+                        foreach ($fields as $field) {
+                            if (trim($row[$field])) {
+                                $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
+                                if ($fieldConf['type'] === 'group') {
+                                    if ($fieldConf['internal_type'] === 'file') {
+                                        // Files...
+                                        if ($fieldConf['MM']) {
+                                            $tempArr = [];
+                                            $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
+                                            $dbAnalysis->start('', 'files', $fieldConf['MM'], $row['uid']);
+                                            foreach ($dbAnalysis->itemArray as $somekey => $someval) {
+                                                if ($someval['id']) {
+                                                    $tempArr[] = $someval['id'];
+                                                }
+                                            }
+                                        } else {
+                                            $tempArr = explode(',', trim($row[$field]));
+                                        }
+                                        foreach ($tempArr as $file) {
+                                            $file = trim($file);
+                                            if ($file) {
+                                                $this->checkFileRefs[$fieldConf['uploadfolder']][$file] += 1;
+                                            }
+                                        }
+                                    }
+                                    if ($fieldConf['internal_type'] === 'db') {
+                                        $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
+                                        $dbAnalysis->start(
+                                            $row[$field],
+                                            $fieldConf['allowed'],
+                                            $fieldConf['MM'],
+                                            $row['uid'],
+                                            $table,
+                                            $fieldConf
+                                        );
+                                        foreach ($dbAnalysis->itemArray as $tempArr) {
+                                            $this->checkGroupDBRefs[$tempArr['table']][$tempArr['id']] += 1;
+                                        }
+                                    }
+                                }
+                                if ($fieldConf['type'] === 'select' && $fieldConf['foreign_table']) {
+                                    $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
+                                    $dbAnalysis->start(
+                                        $row[$field],
+                                        $fieldConf['foreign_table'],
+                                        $fieldConf['MM'],
+                                        $row['uid'],
+                                        $table,
+                                        $fieldConf
+                                    );
+                                    foreach ($dbAnalysis->itemArray as $tempArr) {
+                                        if ($tempArr['id'] > 0) {
+                                            $this->checkGroupDBRefs[$fieldConf['foreign_table']][$tempArr['id']] += 1;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Depends on selectNonEmpty.... to be executed first!!
+     *
+     * @return array Report over files; keys are "moreReferences", "noReferences", "noFile", "error
+     */
+    public function testFileRefs(): array
+    {
+        $output = [];
+        // Handle direct references with upload folder setting (workaround)
+        $newCheckFileRefs = [];
+        foreach ($this->checkFileRefs as $folder => $files) {
+            // Only direct references without a folder setting
+            if ($folder !== '') {
+                $newCheckFileRefs[$folder] = $files;
+                continue;
+            }
+            foreach ($files as $file => $references) {
+                // Direct file references have often many references (removes occurrences in the moreReferences section of the result array)
+                if ($references > 1) {
+                    $references = 1;
+                }
+                // The directory must be empty (prevents checking of the root directory)
+                $directory = PathUtility::dirname($file);
+                if ($directory !== '') {
+                    $newCheckFileRefs[$directory][PathUtility::basename($file)] = $references;
+                }
+            }
+        }
+        $this->checkFileRefs = $newCheckFileRefs;
+        foreach ($this->checkFileRefs as $folder => $fileArr) {
+            $path = Environment::getPublicPath() . '/' . $folder;
+            if (@is_dir($path) && @is_readable($path)) {
+                $d = dir($path);
+                while ($entry = $d->read()) {
+                    if (@is_file($path . '/' . $entry)) {
+                        if (isset($fileArr[$entry])) {
+                            if ($fileArr[$entry] > 1) {
+                                $temp = $this->whereIsFileReferenced($folder, $entry);
+                                $tempList = '';
+                                foreach ($temp as $inf) {
+                                    $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
+                                }
+                                $output['moreReferences'][] = [$path, $entry, $fileArr[$entry], $tempList];
+                            }
+                            unset($fileArr[$entry]);
+                        } else {
+                            // Contains workaround for direct references
+                            if (!strstr($entry, 'index.htm') && !preg_match('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/', $folder)) {
+                                $output['noReferences'][] = [$path, $entry];
+                            }
+                        }
+                    }
+                }
+                $d->close();
+                $tempCounter = 0;
+                foreach ($fileArr as $file => $value) {
+                    // Workaround for direct file references
+                    if (preg_match('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/', $folder)) {
+                        $file = $folder . '/' . $file;
+                        $folder = '';
+                        $path = Environment::getPublicPath();
+                    }
+                    $temp = $this->whereIsFileReferenced($folder, $file);
+                    $tempList = '';
+                    foreach ($temp as $inf) {
+                        $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
+                    }
+                    $tempCounter++;
+                    $output['noFile'][substr($path, -3) . '_' . substr($file, 0, 3) . '_' . $tempCounter] = [$path, $file, $tempList];
+                }
+            } else {
+                $output['error'][] = [$path];
+            }
+        }
+        return $output;
+    }
+
+    /**
+     * Depends on selectNonEmpty.... to be executed first!!
+     *
+     * @param array $theArray Table with key/value pairs being table names and arrays with uid numbers
+     * @return string HTML Error message
+     */
+    public function testDBRefs($theArray): string
+    {
+        $result = '';
+        foreach ($theArray as $table => $dbArr) {
+            if ($GLOBALS['TCA'][$table]) {
+                $ids = array_keys($dbArr);
+                if (!empty($ids)) {
+                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                        ->getQueryBuilderForTable($table);
+                    $queryBuilder->getRestrictions()
+                        ->removeAll()
+                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+                    $queryResult = $queryBuilder
+                        ->select('uid')
+                        ->from($table)
+                        ->where(
+                            $queryBuilder->expr()->in(
+                                'uid',
+                                $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
+                            )
+                        )
+                        ->execute();
+                    while ($row = $queryResult->fetch()) {
+                        if (isset($dbArr[$row['uid']])) {
+                            unset($dbArr[$row['uid']]);
+                        } else {
+                            $result .= 'Strange Error. ...<br />';
+                        }
+                    }
+                    foreach ($dbArr as $theId => $theC) {
+                        $result .= 'There are ' . $theC . ' records pointing to this missing or deleted record; [' . $table . '][' . $theId . ']<br />';
+                    }
+                }
+            } else {
+                $result .= 'Codeerror. Table is not a table...<br />';
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Finding all references to record based on table/uid
+     *
+     * @param string $searchTable Table name
+     * @param int $id Uid of database record
+     * @return array Array with other arrays containing information about where references was found
+     */
+    public function whereIsRecordReferenced($searchTable, $id): array
+    {
+        // Gets tables / Fields that reference to files
+        $fileFields = $this->getDBFields($searchTable);
+        $theRecordList = [];
+        foreach ($fileFields as $info) {
+            list($table, $field) = $info;
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()->removeAll();
+            $queryResult = $queryBuilder
+                ->select('uid', 'pid', $GLOBALS['TCA'][$table]['ctrl']['label'], $field)
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->like(
+                        $field,
+                        $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($id) . '%')
+                    )
+                )
+                ->execute();
+
+            while ($row = $queryResult->fetch()) {
+                // Now this is the field, where the reference COULD come from.
+                // But we're not guaranteed, so we must carefully examine the data.
+                $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
+                $allowedTables = $fieldConf['type'] === 'group' ? $fieldConf['allowed'] : $fieldConf['foreign_table'];
+                $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
+                $dbAnalysis->start($row[$field], $allowedTables, $fieldConf['MM'], $row['uid'], $table, $fieldConf);
+                foreach ($dbAnalysis->itemArray as $tempArr) {
+                    if ($tempArr['table'] == $searchTable && $tempArr['id'] == $id) {
+                        $theRecordList[] = [
+                            'table' => $table,
+                            'uid' => $row['uid'],
+                            'field' => $field,
+                            'pid' => $row['pid']
+                        ];
+                    }
+                }
+            }
+        }
+        return $theRecordList;
+    }
+
+    /**
+     * Finding all references to file based on uploadfolder / filename
+     *
+     * @param string $uploadFolder Upload folder where file is found
+     * @param string $filename Filename to search for
+     * @return array Array with other arrays containing information about where references was found
+     */
+    public function whereIsFileReferenced($uploadFolder, $filename): array
+    {
+        // Gets tables / Fields that reference to files
+        $fileFields = $this->getFileFields($uploadFolder);
+        $theRecordList = [];
+        foreach ($fileFields as $info) {
+            list($table, $field) = $info;
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()->removeAll();
+            $queryResult = $queryBuilder
+                ->select('uid', 'pid', $GLOBALS['TCA'][$table]['ctrl']['label'], $field)
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->like(
+                        $field,
+                        $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($filename) . '%')
+                    )
+                )
+                ->execute();
+            while ($row = $queryResult->fetch()) {
+                // Now this is the field, where the reference COULD come from.
+                // But we're not guaranteed, so we must carefully examine the data.
+                $tempArr = explode(',', trim($row[$field]));
+                foreach ($tempArr as $file) {
+                    $file = trim($file);
+                    if ($file == $filename) {
+                        $theRecordList[] = [
+                            'table' => $table,
+                            'uid' => $row['uid'],
+                            'field' => $field,
+                            'pid' => $row['pid']
+                        ];
+                    }
+                }
+            }
+        }
+        return $theRecordList;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPageIdArray(): array
+    {
+        return $this->pageIdArray;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCheckGroupDBRefs(): array
+    {
+        return $this->checkGroupDBRefs;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCheckSelectDBRefs(): array
+    {
+        return $this->checkSelectDBRefs;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecStats(): array
+    {
+        return $this->recStats;
+    }
+
+    /**
+     * @return array
+     */
+    public function getLRecords(): array
+    {
+        return $this->lRecords;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLostPagesList(): string
+    {
+        return $this->lostPagesList;
+    }
+}