[TASK] Do not set GeneralUtility::$container in unit tests
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / ReferenceIndex.php
index ea1fba7..692e864 100644 (file)
@@ -15,25 +15,25 @@ namespace TYPO3\CMS\Core\Database;
  */
 
 use Doctrine\DBAL\DBALException;
  */
 
 use Doctrine\DBAL\DBALException;
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Registry;
-use TYPO3\CMS\Core\Resource\File;
-use TYPO3\CMS\Core\Resource\Folder;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
 
 /**
  * Reference index processing and relation extraction
  *
  * NOTICE: When the reference index is updated for an offline version the results may not be correct.
 
 /**
  * Reference index processing and relation extraction
  *
  * NOTICE: When the reference index is updated for an offline version the results may not be correct.
- * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyse whole database!)
+ * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyze whole database!)
  * Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
  * When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data
  * structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataStructure! Thus the evaluation will be based
  * Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
  * When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data
  * structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataStructure! Thus the evaluation will be based
@@ -42,8 +42,10 @@ use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
  * maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
  * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
  */
  * maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
  * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
  */
-class ReferenceIndex
+class ReferenceIndex implements LoggerAwareInterface
 {
 {
+    use LoggerAwareTrait;
+
     /**
      * Definition of tables to exclude from the ReferenceIndex
      *
     /**
      * Definition of tables to exclude from the ReferenceIndex
      *
@@ -59,7 +61,6 @@ class ReferenceIndex
      */
     protected static $excludedTables = [
         'sys_log' => true,
      */
     protected static $excludedTables = [
         'sys_log' => true,
-        'sys_history' => true,
         'tx_extensionmanager_domain_model_extension' => true
     ];
 
         'tx_extensionmanager_domain_model_extension' => true
     ];
 
@@ -95,7 +96,9 @@ class ReferenceIndex
      * This array holds the FlexForm references of a record
      *
      * @var array
      * This array holds the FlexForm references of a record
      *
      * @var array
-     * @see getRelations(),FlexFormTools::traverseFlexFormXMLData(),getRelations_flexFormCallBack()
+     * @see getRelations()
+     * @see FlexFormTools::traverseFlexFormXMLData()
+     * @see getRelations_flexFormCallBack()
      */
     public $temp_flexRelations = [];
 
      */
     public $temp_flexRelations = [];
 
@@ -112,11 +115,20 @@ class ReferenceIndex
      * An index of all found references of a single record created in createEntryData() and accumulated in generateRefIndexData()
      *
      * @var array
      * An index of all found references of a single record created in createEntryData() and accumulated in generateRefIndexData()
      *
      * @var array
-     * @see createEntryData(),generateRefIndexData()
+     * @see createEntryData()
+     * @see generateRefIndexData()
      */
     public $relations = [];
 
     /**
      */
     public $relations = [];
 
     /**
+     * A cache to avoid that identical rows are refetched from the database
+     *
+     * @var array
+     * @see getRecordRawCached()
+     */
+    protected $recordCache = [];
+
+    /**
      * Number which we can increase if a change in the code means we will have to force a re-generation of the index.
      *
      * @var int
      * Number which we can increase if a change in the code means we will have to force a re-generation of the index.
      *
      * @var int
@@ -134,16 +146,28 @@ class ReferenceIndex
     /**
      * Runtime Cache to store and retrieve data computed for a single request
      *
     /**
      * Runtime Cache to store and retrieve data computed for a single request
      *
-     * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
+     * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
      */
      */
-    protected $runtimeCache = null;
+    protected $runtimeCache;
 
     /**
 
     /**
-     * Constructor
+     * Enables $runtimeCache and $recordCache
+     * @var bool
      */
      */
-    public function __construct()
+    protected $useRuntimeCache = false;
+
+    /**
+     * @var EventDispatcherInterface
+     */
+    protected $eventDispatcher;
+
+    /**
+     * @param EventDispatcherInterface $eventDispatcher
+     */
+    public function __construct(EventDispatcherInterface $eventDispatcher = null)
     {
     {
-        $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
+        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
+        $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
     }
 
     /**
     }
 
     /**
@@ -161,7 +185,8 @@ class ReferenceIndex
      * Gets the current workspace id
      *
      * @return int
      * Gets the current workspace id
      *
      * @return int
-     * @see updateRefIndexTable(),createEntryData()
+     * @see updateRefIndexTable()
+     * @see createEntryData()
      */
     public function getWorkspaceId()
     {
      */
     public function getWorkspaceId()
     {
@@ -182,7 +207,6 @@ class ReferenceIndex
      */
     public function updateRefIndexTable($tableName, $uid, $testOnly = false)
     {
      */
     public function updateRefIndexTable($tableName, $uid, $testOnly = false)
     {
-
         // First, secure that the index table is not updated with workspace tainted relations:
         $this->WSOL = false;
 
         // First, secure that the index table is not updated with workspace tainted relations:
         $this->WSOL = false;
 
@@ -193,6 +217,11 @@ class ReferenceIndex
             'addedNodes' => 0
         ];
 
             'addedNodes' => 0
         ];
 
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return $result;
+        }
+
         // If this table cannot contain relations, skip it
         if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
             return $result;
         // If this table cannot contain relations, skip it
         if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
             return $result;
@@ -200,7 +229,7 @@ class ReferenceIndex
 
         // Fetch tableRelationFields and save them in cache if not there yet
         $cacheId = static::$cachePrefixTableRelationFields . $tableName;
 
         // Fetch tableRelationFields and save them in cache if not there yet
         $cacheId = static::$cachePrefixTableRelationFields . $tableName;
-        if (!$this->runtimeCache->has($cacheId)) {
+        if (!$this->useRuntimeCache || !$this->runtimeCache->has($cacheId)) {
             $tableRelationFields = $this->fetchTableRelationFields($tableName);
             $this->runtimeCache->set($cacheId, $tableRelationFields);
         } else {
             $tableRelationFields = $this->fetchTableRelationFields($tableName);
             $this->runtimeCache->set($cacheId, $tableRelationFields);
         } else {
@@ -228,22 +257,15 @@ class ReferenceIndex
         }
 
         // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
         }
 
         // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
-        $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
-        $queryBuilder->getRestrictions()->removeAll();
-
-        $exists = $queryBuilder
-            ->select('uid')
-            ->from($tableName)
-            ->where(
-                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
-            )
-            ->execute()
-            ->fetch();
-
-        if ($tableRelationFields !== '' && $exists) {
-            // Then, get relations:
-            $relations = $this->generateRefIndexData($tableName, $uid);
-            if (is_array($relations)) {
+        if ($tableRelationFields !== '') {
+            $existingRecord = $this->getRecordRawCached($tableName, $uid);
+            if ($existingRecord) {
+                // Table has relation fields and record exists - get relations
+                $this->relations = [];
+                $relations = $this->generateDataUsingRecord($tableName, $existingRecord);
+                if (!is_array($relations)) {
+                    return $result;
+                }
                 // Traverse the generated index:
                 foreach ($relations as &$relation) {
                     if (!is_array($relation)) {
                 // Traverse the generated index:
                 foreach ($relations as &$relation) {
                     if (!is_array($relation)) {
@@ -265,8 +287,6 @@ class ReferenceIndex
                     }
                 }
                 $result['relations'] = $relations;
                     }
                 }
                 $result['relations'] = $relations;
-            } else {
-                return $result;
             }
         }
 
             }
         }
 
@@ -277,16 +297,22 @@ class ReferenceIndex
                 $result['deletedNodes'] = count($hashList);
                 $result['deletedNodes_hashList'] = implode(',', $hashList);
                 if (!$testOnly) {
                 $result['deletedNodes'] = count($hashList);
                 $result['deletedNodes_hashList'] = implode(',', $hashList);
                 if (!$testOnly) {
-                    $queryBuilder = $connection->createQueryBuilder();
-                    $queryBuilder
-                        ->delete('sys_refindex')
-                        ->where(
-                            $queryBuilder->expr()->in(
-                                'hash',
-                                $queryBuilder->createNamedParameter($hashList, Connection::PARAM_STR_ARRAY)
+                    $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
+                    foreach (array_chunk($hashList, $maxBindParameters - 10, true) as $chunk) {
+                        if (empty($chunk)) {
+                            continue;
+                        }
+                        $queryBuilder = $connection->createQueryBuilder();
+                        $queryBuilder
+                            ->delete('sys_refindex')
+                            ->where(
+                                $queryBuilder->expr()->in(
+                                    'hash',
+                                    $queryBuilder->createNamedParameter($chunk, Connection::PARAM_STR_ARRAY)
+                                )
                             )
                             )
-                        )
-                        ->execute();
+                            ->execute();
+                    }
                 }
             }
         }
                 }
             }
         }
@@ -300,7 +326,7 @@ class ReferenceIndex
      *
      * @param string $tableName Table name from $GLOBALS['TCA']
      * @param int $uid Record UID
      *
      * @param string $tableName Table name from $GLOBALS['TCA']
      * @param int $uid Record UID
-     * @return array|NULL Index Rows
+     * @return array|null Index Rows
      */
     public function generateRefIndexData($tableName, $uid)
     {
      */
     public function generateRefIndexData($tableName, $uid)
     {
@@ -310,49 +336,61 @@ class ReferenceIndex
 
         $this->relations = [];
 
 
         $this->relations = [];
 
-        // Fetch tableRelationFields and save them in cache if not there yet
-        $cacheId = static::$cachePrefixTableRelationFields . $tableName;
-        if (!$this->runtimeCache->has($cacheId)) {
-            $tableRelationFields = $this->fetchTableRelationFields($tableName);
-            $this->runtimeCache->set($cacheId, $tableRelationFields);
-        } else {
-            $tableRelationFields = $this->runtimeCache->get($cacheId);
+        $record = null;
+        $uid = $uid ? (int)$uid : 0;
+        if ($uid) {
+            // Get raw record from DB
+            $record = $this->getRecordRawCached($tableName, $uid);
         }
 
         }
 
-        // Return if there are no fields which could contain relations
-        if ($tableRelationFields === '') {
-            return array_filter($this->relations);
-        }
-
-        $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
-
-        if ($tableRelationFields === '*') {
-            // If one field of a record is of type flex, all fields have to be fetched
-            // to be passed to FlexFormTools->getDataStructureIdentifier()
-            $selectFields = '*';
-        } else {
-            // otherwise only fields that might contain relations are fetched
-            $selectFields = 'uid,' . $tableRelationFields . ($deleteField ? ',' . $deleteField : '');
+        if (!is_array($record)) {
+            return null;
         }
 
         }
 
-        // Get raw record from DB
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
-        $queryBuilder->getRestrictions()->removeAll();
+        return $this->generateDataUsingRecord($tableName, $record);
+    }
 
 
-        $record = $queryBuilder
-            ->select(...explode(',', $selectFields))
-            ->from($tableName)
+    /**
+     * Returns the amount of references for the given record
+     *
+     * @param string $tableName
+     * @param int $uid
+     * @return int
+     */
+    public function getNumberOfReferencedRecords(string $tableName, int $uid): int
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
+        return (int)$queryBuilder
+            ->count('*')->from('sys_refindex')
             ->where(
             ->where(
-                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
-            )
-            ->execute()
-            ->fetch();
+                $queryBuilder->expr()->eq(
+                    'ref_table',
+                    $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
+                ),
+                $queryBuilder->expr()->eq(
+                    'ref_uid',
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                ),
+                $queryBuilder->expr()->eq(
+                    'deleted',
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                )
+            )->execute()->fetchColumn(0);
+    }
 
 
-        if (!is_array($record)) {
-            return null;
-        }
+    /**
+     * Calculate the relations for a record of a given table
+     *
+     * @param string $tableName Table being processed
+     * @param array $record Record from $tableName
+     * @return array
+     */
+    protected function generateDataUsingRecord(string $tableName, array $record): array
+    {
+        $this->relations = [];
+        $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
 
 
-        // Deleted:
+        // Is the record deleted?
         $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
 
         // Get all relations from record:
         $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
 
         // Get all relations from record:
@@ -362,39 +400,27 @@ class ReferenceIndex
             // Based on type
             switch ((string)$fieldRelations['type']) {
                 case 'db':
             // Based on type
             switch ((string)$fieldRelations['type']) {
                 case 'db':
-                    $this->createEntryData_dbRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['itemArray']);
-                    break;
-                case 'file_reference':
-                    // not used (see getRelations()), but fallback to file
-                case 'file':
-                    $this->createEntryData_fileRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['newValueFiles']);
+                    $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, '', $deleted, $fieldRelations['itemArray']);
                     break;
                 case 'flex':
                     // DB references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['db'])) {
                         foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
                     break;
                 case 'flex':
                     // DB references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['db'])) {
                         foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
-                            $this->createEntryData_dbRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
-                        }
-                    }
-                    // File references in FlexForms
-                    // @todo #65463 Test correct handling of file references in FlexForms
-                    if (is_array($fieldRelations['flexFormRels']['file'])) {
-                        foreach ($fieldRelations['flexFormRels']['file'] as $flexPointer => $subList) {
-                            $this->createEntryData_fileRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
+                            $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $subList);
                         }
                     }
                     // Soft references in FlexForms
                     // @todo #65464 Test correct handling of soft references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
                         foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
                         }
                     }
                     // Soft references in FlexForms
                     // @todo #65464 Test correct handling of soft references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
                         foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
-                            $this->createEntryData_softreferences($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList['keys']);
+                            $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $subList['keys']);
                         }
                     }
                     break;
             }
             // Soft references in the field
             if (is_array($fieldRelations['softrefs'])) {
                         }
                     }
                     break;
             }
             // Soft references in the field
             if (is_array($fieldRelations['softrefs'])) {
-                $this->createEntryData_softreferences($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
+                $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
             }
         }
 
             }
         }
 
@@ -410,9 +436,9 @@ class ReferenceIndex
      * @param string $field Fieldname of source record (where reference is located)
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
      * @param string $field Fieldname of source record (where reference is located)
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
-     * @param string $ref_table For database references; the tablename the reference points to. Special keyword "_FILE" indicates that "ref_string" is a file reference either absolute or relative to PATH_site. Special keyword "_STRING" indicates some special usage (typ. softreference) where "ref_string" is used for the value.
-     * @param int $ref_uid For database references; The UID of the record (zero "ref_table" is "_FILE" or "_STRING")
-     * @param string $ref_string For "_FILE" or "_STRING" references: The filepath (relative to PATH_site or absolute) or other string.
+     * @param string $ref_table For database references; the tablename the reference points to. Special keyword "_STRING" indicates some special usage (typ. softreference) where "ref_string" is used for the value.
+     * @param int $ref_uid For database references; The UID of the record (zero "ref_table" is "_STRING")
+     * @param string $ref_string For "_STRING" references: The string.
      * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
      * @param string $softref_key If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
      * @param string $softref_id Soft reference ID for key. Might be useful for replace operations.
      * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
      * @param string $softref_key If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
      * @param string $softref_id Soft reference ID for key. Might be useful for replace operations.
@@ -420,30 +446,64 @@ class ReferenceIndex
      */
     public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
     {
      */
     public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
     {
-        if ($this->getWorkspaceId() > 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
-            $element = BackendUtility::getRecord($table, $uid, 't3ver_wsid');
-            if ($element !== null
-                && isset($element['t3ver_wsid'])
-                && (int)$element['t3ver_wsid'] !== $this->getWorkspaceId()
-            ) {
-                // The given element is ws-enabled but doesn't live in the selected workspace
-                // => don't add to index as it's not actually there
-                return null;
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return null;
+        }
+        return $this->createEntryDataUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$field,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (string)$ref_table,
+            $ref_uid ? (int)$ref_uid : 0,
+            (string)$ref_string,
+            $sort ? (int)$sort : 0,
+            (string)$softref_key,
+            (string)$softref_id
+        );
+    }
+
+    /**
+     * Create array with field/value pairs ready to insert in database
+     *
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $table
+     * @param string $fieldName Fieldname of source record (where reference is located)
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [$field]
+     * @param int $deleted Whether record is deleted-flagged or not
+     * @param string $referencedTable In database references the tablename the reference points to. Keyword "_STRING" indicates special usage (typ. SoftReference) in $referenceString
+     * @param int $referencedUid In database references the UID of the record (zero $referencedTable is "_STRING")
+     * @param string $referenceString For "_STRING" references: The string.
+     * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
+     * @param string $softReferenceKey If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
+     * @param string $softReferenceId Soft reference ID for key. Might be useful for replace operations.
+     * @return array|bool Array to insert in DB or false if record should not be processed
+     */
+    protected function createEntryDataUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, string $referencedTable, int $referencedUid, string $referenceString = '', int $sort = -1, string $softReferenceKey = '', string $softReferenceId = '')
+    {
+        $workspaceId = 0;
+        if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
+            $workspaceId = $this->getWorkspaceId();
+            if (isset($record['t3ver_wsid']) && (int)$record['t3ver_wsid'] !== $workspaceId) {
+                // The given record is workspace-enabled but doesn't live in the selected workspace => don't add index as it's not actually there
+                return false;
             }
         }
         return [
             }
         }
         return [
-            'tablename' => $table,
-            'recuid' => $uid,
-            'field' => $field,
+            'tablename' => $tableName,
+            'recuid' => $record['uid'],
+            'field' => $fieldName,
             'flexpointer' => $flexPointer,
             'flexpointer' => $flexPointer,
-            'softref_key' => $softref_key,
-            'softref_id' => $softref_id,
+            'softref_key' => $softReferenceKey,
+            'softref_id' => $softReferenceId,
             'sorting' => $sort,
             'sorting' => $sort,
-            'deleted' => $deleted,
-            'workspace' => $this->getWorkspaceId(),
-            'ref_table' => $ref_table,
-            'ref_uid' => $ref_uid,
-            'ref_string' => mb_substr($ref_string, 0, 1024)
+            'deleted' => (int)$deleted,
+            'workspace' => $workspaceId,
+            'ref_table' => $referencedTable,
+            'ref_uid' => $referencedUid,
+            'ref_string' => mb_substr($referenceString, 0, 1024)
         ];
     }
 
         ];
     }
 
@@ -459,29 +519,34 @@ class ReferenceIndex
      */
     public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
     {
      */
     public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
     {
-        foreach ($items as $sort => $i) {
-            $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $i['table'], $i['id'], '', $sort);
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return;
         }
         }
+        $this->createEntryDataForDatabaseRelationsUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$fieldName,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (array)$items
+        );
     }
 
     /**
     }
 
     /**
-     * Enter file references to ->relations array
+     * Add database references to ->relations array based on fetched record
      *
      *
-     * @param string $table Tablename of source record (where reference is located)
-     * @param int $uid UID of source record (where reference is located)
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $tableName
      * @param string $fieldName Fieldname of source record (where reference is located)
      * @param string $fieldName Fieldname of source record (where reference is located)
-     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
      * @param int $deleted Whether record is deleted-flagged or not
      * @param int $deleted Whether record is deleted-flagged or not
-     * @param array $items Data array with file relations
+     * @param array $items Data array with database relations (table/id)
      */
      */
-    public function createEntryData_fileRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
+    protected function createEntryDataForDatabaseRelationsUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, array $items)
     {
         foreach ($items as $sort => $i) {
     {
         foreach ($items as $sort => $i) {
-            $filePath = $i['ID_absFile'];
-            if (GeneralUtility::isFirstPartOfStr($filePath, PATH_site)) {
-                $filePath = PathUtility::stripPathSitePrefix($filePath);
-            }
-            $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $filePath, $sort);
+            $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $i['table'], (int)$i['id'], '', $sort);
         }
     }
 
         }
     }
 
@@ -497,25 +562,44 @@ class ReferenceIndex
      */
     public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
     {
      */
     public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
     {
-        if (is_array($keys)) {
-            foreach ($keys as $spKey => $elements) {
-                if (is_array($elements)) {
-                    foreach ($elements as $subKey => $el) {
-                        if (is_array($el['subst'])) {
-                            switch ((string)$el['subst']['type']) {
-                                case 'db':
-                                    list($tableName, $recordId) = explode(':', $el['subst']['recordRef']);
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $tableName, $recordId, '', -1, $spKey, $subKey);
-                                    break;
-                                case 'file_reference':
-                                    // not used (see getRelations()), but fallback to file
-                                case 'file':
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $el['subst']['relFileName'], -1, $spKey, $subKey);
-                                    break;
-                                case 'string':
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
-                                    break;
-                            }
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid || !is_array($keys)) {
+            return;
+        }
+        $this->createEntryDataForSoftReferencesUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$fieldName,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (array)$keys
+        );
+    }
+
+    /**
+     * Add SoftReference references to ->relations array based on fetched record
+     *
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $tableName
+     * @param string $fieldName Fieldname of source record (where reference is located)
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
+     * @param int $deleted Whether record is deleted-flagged or not
+     * @param array $keys Data array with soft reference keys
+     */
+    protected function createEntryDataForSoftReferencesUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, array $keys)
+    {
+        foreach ($keys as $spKey => $elements) {
+            if (is_array($elements)) {
+                foreach ($elements as $subKey => $el) {
+                    if (is_array($el['subst'])) {
+                        switch ((string)$el['subst']['type']) {
+                            case 'db':
+                                list($referencedTable, $referencedUid) = explode(':', $el['subst']['recordRef']);
+                                $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $referencedTable, (int)$referencedUid, '', -1, $spKey, $subKey);
+                                break;
+                            case 'string':
+                                $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
+                                break;
                         }
                     }
                 }
                         }
                     }
                 }
@@ -532,7 +616,7 @@ class ReferenceIndex
     /**
      * Returns relation information for a $table/$row-array
      * Traverses all fields in input row which are configured in TCA/columns
     /**
      * Returns relation information for a $table/$row-array
      * Traverses all fields in input row which are configured in TCA/columns
-     * It looks for hard relations to files and records in the TCA types "select" and "group"
+     * It looks for hard relations to records in the TCA types "select" and "group"
      *
      * @param string $table Table name
      * @param array $row Row from table
      *
      * @param string $table Table name
      * @param array $row Row from table
@@ -548,42 +632,12 @@ class ReferenceIndex
         foreach ($row as $field => $value) {
             if ($this->shouldExcludeTableColumnFromReferenceIndex($table, $field, $onlyField) === false) {
                 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
         foreach ($row as $field => $value) {
             if ($this->shouldExcludeTableColumnFromReferenceIndex($table, $field, $onlyField) === false) {
                 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
-                // Add files
-                $resultsFromFiles = $this->getRelations_procFiles($value, $conf, $uid);
-                if (!empty($resultsFromFiles)) {
-                    // We have to fill different arrays here depending on the result.
-                    // internal_type file is still a relation of type file and
-                    // since http://forge.typo3.org/issues/49538 internal_type file_reference
-                    // is a database relation to a sys_file record
-                    $fileResultsFromFiles = [];
-                    $dbResultsFromFiles = [];
-                    foreach ($resultsFromFiles as $resultFromFiles) {
-                        if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
-                            $dbResultsFromFiles[] = $resultFromFiles;
-                        } else {
-                            // Creates an entry for the field with all the files:
-                            $fileResultsFromFiles[] = $resultFromFiles;
-                        }
-                    }
-                    if (!empty($fileResultsFromFiles)) {
-                        $outRow[$field] = [
-                            'type' => 'file',
-                            'newValueFiles' => $fileResultsFromFiles
-                        ];
-                    }
-                    if (!empty($dbResultsFromFiles)) {
-                        $outRow[$field] = [
-                            'type' => 'db',
-                            'itemArray' => $dbResultsFromFiles
-                        ];
-                    }
-                }
                 // Add a softref definition for link fields if the TCA does not specify one already
                 if ($conf['type'] === 'input' && $conf['renderType'] === 'inputLink' && empty($conf['softref'])) {
                     $conf['softref'] = 'typolink';
                 }
                 // Add DB:
                 // Add a softref definition for link fields if the TCA does not specify one already
                 if ($conf['type'] === 'input' && $conf['renderType'] === 'inputLink' && empty($conf['softref'])) {
                     $conf['softref'] = 'typolink';
                 }
                 // Add DB:
-                $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table, $field);
+                $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table);
                 if (!empty($resultsFromDatabase)) {
                     // Create an entry for the field with all DB relations:
                     $outRow[$field] = [
                 if (!empty($resultsFromDatabase)) {
                     // Create an entry for the field with all DB relations:
                     $outRow[$field] = [
@@ -591,17 +645,16 @@ class ReferenceIndex
                         'itemArray' => $resultsFromDatabase
                     ];
                 }
                         'itemArray' => $resultsFromDatabase
                     ];
                 }
-                // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
+                // For "flex" fieldtypes we need to traverse the structure looking for db references of course!
                 if ($conf['type'] === 'flex') {
                     // Get current value array:
                     // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up
                     // the note in the JavaDoc documentation for the function FlexFormTools->getDataStructureIdentifier()
                     $currentValueArray = GeneralUtility::xml2array($value);
                 if ($conf['type'] === 'flex') {
                     // Get current value array:
                     // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up
                     // the note in the JavaDoc documentation for the function FlexFormTools->getDataStructureIdentifier()
                     $currentValueArray = GeneralUtility::xml2array($value);
-                    // Traversing the XML structure, processing files:
+                    // Traversing the XML structure, processing:
                     if (is_array($currentValueArray)) {
                         $this->temp_flexRelations = [
                             'db' => [],
                     if (is_array($currentValueArray)) {
                         $this->temp_flexRelations = [
                             'db' => [],
-                            'file' => [],
                             'softrefs' => []
                         ];
                         // Create and call iterator object:
                             'softrefs' => []
                         ];
                         // Create and call iterator object:
@@ -617,8 +670,8 @@ class ReferenceIndex
                 // Soft References:
                 if ((string)$value !== '') {
                     $softRefValue = $value;
                 // Soft References:
                 if ((string)$value !== '') {
                     $softRefValue = $value;
-                    $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
-                    if ($softRefs !== false) {
+                    if (!empty($conf['softref'])) {
+                        $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
                         foreach ($softRefs as $spKey => $spParams) {
                             $softRefObj = BackendUtility::softRefParserObj($spKey);
                             if (is_object($softRefObj)) {
                         foreach ($softRefs as $spKey => $spParams) {
                             $softRefObj = BackendUtility::softRefParserObj($spKey);
                             if (is_object($softRefObj)) {
@@ -642,16 +695,16 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Callback function for traversing the FlexForm structure in relation to finding file and DB references!
+     * Callback function for traversing the FlexForm structure in relation to finding DB references!
      *
      * @param array $dsArr Data structure for the current value
      * @param mixed $dataValue Current value
      * @param array $PA Additional configuration used in calling function
      * @param string $structurePath Path of value in DS structure
      *
      * @param array $dsArr Data structure for the current value
      * @param mixed $dataValue Current value
      * @param array $PA Additional configuration used in calling function
      * @param string $structurePath Path of value in DS structure
-     * @param object $parentObject Object reference to caller (unused)
-     * @see DataHandler::checkValue_flex_procInData_travDS(),FlexFormTools::traverseFlexFormXMLData()
+     * @see DataHandler::checkValue_flex_procInData_travDS()
+     * @see FlexFormTools::traverseFlexFormXMLData()
      */
      */
-    public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $parentObject)
+    public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath)
     {
         // Removing "data/" in the beginning of path (which points to location in data array)
         $structurePath = substr($structurePath, 5) . '/';
     {
         // Removing "data/" in the beginning of path (which points to location in data array)
         $structurePath = substr($structurePath, 5) . '/';
@@ -662,35 +715,12 @@ class ReferenceIndex
             $PA['uid'],
             $PA['field']
         ];
             $PA['uid'],
             $PA['field']
         ];
-        // Add files
-        $resultsFromFiles = $this->getRelations_procFiles($dataValue, $dsConf, $uid);
-        if (!empty($resultsFromFiles)) {
-            // We have to fill different arrays here depending on the result.
-            // internal_type file is still a relation of type file and
-            // since http://forge.typo3.org/issues/49538 internal_type file_reference
-            // is a database relation to a sys_file record
-            $fileResultsFromFiles = [];
-            $dbResultsFromFiles = [];
-            foreach ($resultsFromFiles as $resultFromFiles) {
-                if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
-                    $dbResultsFromFiles[] = $resultFromFiles;
-                } else {
-                    $fileResultsFromFiles[] = $resultFromFiles;
-                }
-            }
-            if (!empty($fileResultsFromFiles)) {
-                $this->temp_flexRelations['file'][$structurePath] = $fileResultsFromFiles;
-            }
-            if (!empty($dbResultsFromFiles)) {
-                $this->temp_flexRelations['db'][$structurePath] = $dbResultsFromFiles;
-            }
-        }
         // Add a softref definition for link fields if the TCA does not specify one already
         if ($dsConf['type'] === 'input' && $dsConf['renderType'] === 'inputLink' && empty($dsConf['softref'])) {
             $dsConf['softref'] = 'typolink';
         }
         // Add DB:
         // Add a softref definition for link fields if the TCA does not specify one already
         if ($dsConf['type'] === 'input' && $dsConf['renderType'] === 'inputLink' && empty($dsConf['softref'])) {
             $dsConf['softref'] = 'typolink';
         }
         // Add DB:
-        $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table, $field);
+        $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table);
         if (!empty($resultsFromDatabase)) {
             // Create an entry for the field with all DB relations:
             $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
         if (!empty($resultsFromDatabase)) {
             // Create an entry for the field with all DB relations:
             $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
@@ -720,87 +750,28 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Check field configuration if it is a file relation field and extract file relations if any
-     *
-     * @param string $value Field value
-     * @param array $conf Field configuration array of type "TCA/columns
-     * @param int $uid Field uid
-     * @return bool|array If field type is OK it will return an array with the files inside. Else FALSE
-     */
-    public function getRelations_procFiles($value, $conf, $uid)
-    {
-        if ($conf['type'] !== 'group' || ($conf['internal_type'] !== 'file' && $conf['internal_type'] !== 'file_reference')) {
-            return false;
-        }
-
-        // Collect file values in array:
-        if ($conf['MM']) {
-            $theFileValues = [];
-            $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
-            $dbAnalysis->start('', 'files', $conf['MM'], $uid);
-            foreach ($dbAnalysis->itemArray as $someval) {
-                if ($someval['id']) {
-                    $theFileValues[] = $someval['id'];
-                }
-            }
-        } else {
-            $theFileValues = explode(',', $value);
-        }
-        // Traverse the files and add them:
-        $uploadFolder = $conf['internal_type'] === 'file' ? $conf['uploadfolder'] : '';
-        $destinationFolder = $this->destPathFromUploadFolder($uploadFolder);
-        $newValueFiles = [];
-        foreach ($theFileValues as $file) {
-            if (trim($file)) {
-                $realFile = $destinationFolder . '/' . trim($file);
-                $newValueFile = [
-                    'filename' => basename($file),
-                    'ID' => md5($realFile),
-                    'ID_absFile' => $realFile
-                ];
-                // Set sys_file and id for referenced files
-                if ($conf['internal_type'] === 'file_reference') {
-                    try {
-                        $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($file);
-                        if ($file instanceof File || $file instanceof Folder) {
-                            // For setting this as sys_file relation later, the keys filename, ID and ID_absFile
-                            // have not to be included, because the are not evaluated for db relations.
-                            $newValueFile = [
-                                'table' => 'sys_file',
-                                'id' => $file->getUid()
-                            ];
-                        }
-                    } catch (\Exception $e) {
-                    }
-                }
-                $newValueFiles[] = $newValueFile;
-            }
-        }
-        return $newValueFiles;
-    }
-
-    /**
      * Check field configuration if it is a DB relation field and extract DB relations if any
      *
      * @param string $value Field value
      * @param array $conf Field configuration array of type "TCA/columns
      * @param int $uid Field uid
      * @param string $table Table name
      * Check field configuration if it is a DB relation field and extract DB relations if any
      *
      * @param string $value Field value
      * @param array $conf Field configuration array of type "TCA/columns
      * @param int $uid Field uid
      * @param string $table Table name
-     * @param string $field Field name
-     * @return array If field type is OK it will return an array with the database relations. Else FALSE
+     * @return array|bool If field type is OK it will return an array with the database relations. Else FALSE
      */
      */
-    public function getRelations_procDB($value, $conf, $uid, $table = '', $field = '')
+    public function getRelations_procDB($value, $conf, $uid, $table = '')
     {
         // Get IRRE relations
         if (empty($conf)) {
             return false;
     {
         // Get IRRE relations
         if (empty($conf)) {
             return false;
-        } elseif ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
+        }
+        if ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->setUseLiveReferenceIds(false);
             $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
             return $dbAnalysis->itemArray;
             // DB record lists:
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->setUseLiveReferenceIds(false);
             $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
             return $dbAnalysis->itemArray;
             // DB record lists:
-        } elseif ($this->isDbReferenceField($conf)) {
+        }
+        if ($this->isDbReferenceField($conf)) {
             $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
             if ($conf['MM_opposite_field']) {
                 return [];
             $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
             if ($conf['MM_opposite_field']) {
                 return [];
@@ -895,14 +866,6 @@ class ReferenceIndex
                                 return $error;
                             }
                             break;
                                 return $error;
                             }
                             break;
-                        case 'file_reference':
-                            // not used (see getRelations()), but fallback to file
-                        case 'file':
-                            $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['newValueFiles'], $newValue, $dataArray);
-                            if ($error) {
-                                return $error;
-                            }
-                            break;
                         case 'flex':
                             // DB references in FlexForms
                             if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
                         case 'flex':
                             // DB references in FlexForms
                             if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
@@ -911,13 +874,6 @@ class ReferenceIndex
                                     return $error;
                                 }
                             }
                                     return $error;
                                 }
                             }
-                            // File references in FlexForms
-                            if (is_array($fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']])) {
-                                $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
-                                if ($error) {
-                                    return $error;
-                                }
-                            }
                             // Soft references in FlexForms
                             if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
                                 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
                             // Soft references in FlexForms
                             if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
                                 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
@@ -937,21 +893,19 @@ class ReferenceIndex
                     // Data Array, now ready to be sent to DataHandler
                     if ($returnDataArray) {
                         return $dataArray;
                     // Data Array, now ready to be sent to DataHandler
                     if ($returnDataArray) {
                         return $dataArray;
-                    } else {
-                        // Execute CMD array:
-                        $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
-                        $dataHandler->dontProcessTransformations = true;
-                        $dataHandler->bypassWorkspaceRestrictions = true;
-                        $dataHandler->bypassFileHandling = true;
-                        // Otherwise this cannot update things in deleted records...
-                        $dataHandler->bypassAccessCheckForRecords = true;
-                        // Check has been done previously that there is a backend user which is Admin and also in live workspace
-                        $dataHandler->start($dataArray, []);
-                        $dataHandler->process_datamap();
-                        // Return errors if any:
-                        if (!empty($dataHandler->errorLog)) {
-                            return LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $dataHandler->errorLog);
-                        }
+                    }
+                    // Execute CMD array:
+                    $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
+                    $dataHandler->dontProcessTransformations = true;
+                    $dataHandler->bypassWorkspaceRestrictions = true;
+                    // Otherwise this cannot update things in deleted records...
+                    $dataHandler->bypassAccessCheckForRecords = true;
+                    // Check has been done previously that there is a backend user which is Admin and also in live workspace
+                    $dataHandler->start($dataArray, []);
+                    $dataHandler->process_datamap();
+                    // Return errors if any:
+                    if (!empty($dataHandler->errorLog)) {
+                        return LF . 'DataHandler:' . implode(LF . 'DataHandler:', $dataHandler->errorLog);
                     }
                 }
             }
                     }
                 }
             }
@@ -1003,51 +957,10 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Setting a value for a reference for a FILE field:
-     *
-     * @param array $refRec sys_refindex record
-     * @param array $itemArray Array of references from that field
-     * @param string $newValue Value to substitute current value with (or NULL to unset it)
-     * @param array $dataArray Data array in which the new value is set (passed by reference)
-     * @param string $flexPointer Flexform pointer, if in a flex form field.
-     * @return string Error message if any, otherwise FALSE = OK
-     */
-    public function setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
-    {
-        $ID_absFile = PathUtility::stripPathSitePrefix($itemArray[$refRec['sorting']]['ID_absFile']);
-        if ($ID_absFile === (string)$refRec['ref_string'] && $refRec['ref_table'] === '_FILE') {
-            // Setting or removing value:
-            // Remove value:
-            if ($newValue === null) {
-                unset($itemArray[$refRec['sorting']]);
-            } else {
-                $itemArray[$refRec['sorting']]['filename'] = $newValue;
-            }
-            // Traverse and compile new list of records:
-            $saveValue = [];
-            foreach ($itemArray as $fileInfo) {
-                $saveValue[] = $fileInfo['filename'];
-            }
-            // Set in data array:
-            if ($flexPointer) {
-                $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
-                $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
-            } else {
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
-            }
-        } else {
-            return 'ERROR: either "' . $refRec['ref_table'] . '" was not "_FILE" or file PATH_site+"' . $refRec['ref_string'] . '" did not match that of the record ("' . $itemArray[$refRec['sorting']]['ID_absFile'] . '") in sorting index "' . $refRec['sorting'] . '"';
-        }
-
-        return false;
-    }
-
-    /**
      * Setting a value for a soft reference token
      *
      * @param array $refRec sys_refindex record
      * Setting a value for a soft reference token
      *
      * @param array $refRec sys_refindex record
-     * @param array $softref Array of soft reference occurencies
+     * @param array $softref Array of soft reference occurrences
      * @param string $newValue Value to substitute current value with
      * @param array $dataArray Data array in which the new value is set (passed by reference)
      * @param string $flexPointer Flexform pointer, if in a flex form field.
      * @param string $newValue Value to substitute current value with
      * @param array $dataArray Data array in which the new value is set (passed by reference)
      * @param string $flexPointer Flexform pointer, if in a flex form field.
@@ -1068,7 +981,7 @@ class ReferenceIndex
             }
         }
         // Set in data array:
             }
         }
         // Set in data array:
-        if (!strstr($softref['tokenizedContent'], '{softref:')) {
+        if (strpos($softref['tokenizedContent'], '{softref:') === false) {
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
@@ -1117,8 +1030,6 @@ class ReferenceIndex
         return
             $this->isDbReferenceField($configuration)
             ||
         return
             $this->isDbReferenceField($configuration)
             ||
-            ($configuration['type'] === 'group' && ($configuration['internal_type'] === 'file' || $configuration['internal_type'] === 'file_reference')) // getRelations_procFiles
-            ||
             ($configuration['type'] === 'input' && $configuration['renderType'] === 'inputLink') // getRelations_procDB
             ||
             $configuration['type'] === 'flex'
             ($configuration['type'] === 'input' && $configuration['renderType'] === 'inputLink') // getRelations_procDB
             ||
             $configuration['type'] === 'flex'
@@ -1160,20 +1071,6 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Returns destination path to an upload folder given by $folder
-     *
-     * @param string $folder Folder relative to PATH_site
-     * @return string Input folder prefixed with PATH_site. No checking for existence is done. Output must be a folder without trailing slash.
-     */
-    public function destPathFromUploadFolder($folder)
-    {
-        if (!$folder) {
-            return substr(PATH_site, 0, -1);
-        }
-        return PATH_site . $folder;
-    }
-
-    /**
      * Updating Index (External API)
      *
      * @param bool $testOnly If set, only a test
      * Updating Index (External API)
      *
      * @param bool $testOnly If set, only a test
@@ -1217,9 +1114,11 @@ class ReferenceIndex
                     ->from($tableName)
                     ->execute();
             } catch (DBALException $e) {
                     ->from($tableName)
                     ->execute();
             } catch (DBALException $e) {
-                // Table exists in $TCA but does not exist in the database
-                // @todo: improve / change message and add actual sql error?
-                GeneralUtility::sysLog(sprintf('Table "%s" exists in $TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.', $tableName), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
+                // Table exists in TCA but does not exist in the database
+                $msg = 'Table "' .
+                        $tableName .
+                        '" exists in TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.';
+                $this->logger->error($msg, ['exception' => $e]);
                 continue;
             }
 
                 continue;
             }
 
@@ -1243,14 +1142,7 @@ class ReferenceIndex
 
             // Subselect based queries only work on the same connection
             if ($refIndexConnectionName !== $tableConnectionName) {
 
             // Subselect based queries only work on the same connection
             if ($refIndexConnectionName !== $tableConnectionName) {
-                GeneralUtility::sysLog(
-                    sprintf(
-                        'Not checking table "%s" for lost indexes, "sys_refindex" table uses a different connection',
-                        $tableName
-                    ),
-                    'core',
-                    GeneralUtility::SYSLOG_SEVERITY_ERROR
-                );
+                $this->logger->error('Not checking table "' . $tableName . '" for lost indexes, "sys_refindex" table uses a different connection');
                 continue;
             }
 
                 continue;
             }
 
@@ -1344,12 +1236,9 @@ class ReferenceIndex
             $recordsCheckedString,
             $errorCount ? FlashMessage::ERROR : FlashMessage::OK
         );
             $recordsCheckedString,
             $errorCount ? FlashMessage::ERROR : FlashMessage::OK
         );
-        /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
-        /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
-        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
-        $defaultFlashMessageQueue->enqueue($flashMessage);
-        $bodyContent = $defaultFlashMessageQueue->renderFlashMessages();
+
+        $flashMessageRenderer = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve();
+        $bodyContent = $flashMessageRenderer->render([$flashMessage]);
         if ($cli_echo) {
             echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
         }
         if ($cli_echo) {
             echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
         }
@@ -1361,6 +1250,82 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
+     * Gets one record from database and stores it in an internal cache (which expires along with object lifecycle) for faster retrieval
+     *
+     * Assumption:
+     *
+     * - This method is only used from within delegate methods and so only caches queries generated based on the record being indexed; the query
+     *   to select origin side record is uncached
+     * - Origin side records do not change in database while updating the reference index
+     * - Origin record does not get removed while updating index
+     * - Relations may change during indexing, which is why only the origin record is cached and all relations are re-process even when repeating
+     *   indexing of the same origin record
+     *
+     * Please note that the cache is disabled by default but can be enabled using $this->enableRuntimeCaches()
+     * due to possible side-effects while handling references that were changed during one single
+     * request.
+     *
+     * @param string $tableName
+     * @param int $uid
+     * @return array|false
+     */
+    protected function getRecordRawCached(string $tableName, int $uid)
+    {
+        $recordCacheId = $tableName . ':' . $uid;
+        if (!$this->useRuntimeCache || !isset($this->recordCache[$recordCacheId])) {
+
+            // Fetch fields of the table which might contain relations
+            $cacheId = static::$cachePrefixTableRelationFields . $tableName;
+            if (!$this->useRuntimeCache || !$this->runtimeCache->has($cacheId)) {
+                $tableRelationFields = $this->fetchTableRelationFields($tableName);
+                if ($this->useRuntimeCache) {
+                    $this->runtimeCache->set($cacheId, $tableRelationFields);
+                }
+            } else {
+                $tableRelationFields = $this->runtimeCache->get($cacheId);
+            }
+
+            // Return if there are no fields which could contain relations
+            if ($tableRelationFields === '') {
+                return $this->relations;
+            }
+
+            if ($tableRelationFields === '*') {
+                // If one field of a record is of type flex, all fields have to be fetched to be passed to FlexFormTools->getDataStructureIdentifier()
+                $selectFields = '*';
+            } else {
+                // otherwise only fields that might contain relations are fetched
+                $selectFields = 'uid,' . $tableRelationFields;
+                $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
+                if ($deleteField) {
+                    $selectFields .= ',' . $deleteField;
+                }
+                if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
+                    $selectFields .= ',t3ver_wsid';
+                }
+            }
+
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
+            $row = $queryBuilder
+                ->select(...GeneralUtility::trimExplode(',', $selectFields, true))
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'uid',
+                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                    )
+                )
+                ->execute()
+                ->fetch();
+
+            $this->recordCache[$recordCacheId] = $row;
+        }
+        return $this->recordCache[$recordCacheId];
+    }
+
+    /**
      * Checks if a given table should be excluded from ReferenceIndex
      *
      * @param string $tableName Name of the table
      * Checks if a given table should be excluded from ReferenceIndex
      *
      * @param string $tableName Name of the table
@@ -1372,13 +1337,11 @@ class ReferenceIndex
             return static::$excludedTables[$tableName];
         }
 
             return static::$excludedTables[$tableName];
         }
 
-        // Only exclude tables from ReferenceIndex which do not contain any relations and never did since existing references won't be deleted!
-        // There is no need to add tables without a definition in $GLOBALS['TCA] since ReferenceIndex only handles those.
-        $excludeTable = false;
-        $signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
-        $signalSlotDispatcher->dispatch(__CLASS__, 'shouldExcludeTableFromReferenceIndex', [$tableName, &$excludeTable]);
-
-        static::$excludedTables[$tableName] = $excludeTable;
+        // Only exclude tables from ReferenceIndex which do not contain any relations and never
+        // did since existing references won't be deleted!
+        $event = new IsTableExcludedFromReferenceIndexEvent($tableName);
+        $event = $this->eventDispatcher->dispatch($event);
+        static::$excludedTables[$tableName] = $event->isTableExcluded();
 
         return static::$excludedTables[$tableName];
     }
 
         return static::$excludedTables[$tableName];
     }
@@ -1405,6 +1368,24 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
+     * Enables the runtime-based caches
+     * Could lead to side effects, depending if the reference index instance is run multiple times
+     * while records would be changed.
+     */
+    public function enableRuntimeCache()
+    {
+        $this->useRuntimeCache = true;
+    }
+
+    /**
+     * Disables the runtime-based cache
+     */
+    public function disableRuntimeCache()
+    {
+        $this->useRuntimeCache = false;
+    }
+
+    /**
      * Returns the current BE user.
      *
      * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
      * Returns the current BE user.
      *
      * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication