8607e253810dcddc0fa2b4a2b409e997ca830cbb
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / ReferenceIndex.php
1 <?php
2
3 /*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 namespace TYPO3\CMS\Core\Database;
17
18 use Doctrine\DBAL\Exception as DBALException;
19 use Psr\EventDispatcher\EventDispatcherInterface;
20 use Psr\Log\LoggerAwareInterface;
21 use Psr\Log\LoggerAwareTrait;
22 use Psr\Log\LogLevel;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
24 use TYPO3\CMS\Backend\View\ProgressListenerInterface;
25 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
26 use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
27 use TYPO3\CMS\Core\DataHandling\DataHandler;
28 use TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent;
29 use TYPO3\CMS\Core\Registry;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Versioning\VersionState;
32
33 /**
34 * Reference index processing and relation extraction
35 *
36 * NOTICE: When the reference index is updated for an offline version the results may not be correct.
37 * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyze whole database!)
38 * 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.
39 * 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
40 * 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
41 * on the Data Structure set in the Live workspace!
42 * Somehow this scenario is rarely going to happen. Yet, it is an inconsistency and I see now practical way to handle it - other than simply ignoring
43 * 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?
44 * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
45 */
46 class ReferenceIndex implements LoggerAwareInterface
47 {
48 use LoggerAwareTrait;
49
50 /**
51 * Definition of tables to exclude from the ReferenceIndex
52 *
53 * Only tables which do not contain any relations and never did so far since references also won't be deleted for
54 * these. Since only tables with an entry in $GLOBALS['TCA] are handled by ReferenceIndex there is no need to add
55 * *_mm-tables.
56 *
57 * Implemented as array with fields as keys and booleans as values for fast isset() lookup instead of slow in_array()
58 *
59 * @var array
60 * @see updateRefIndexTable()
61 * @see shouldExcludeTableFromReferenceIndex()
62 */
63 protected array $excludedTables = [
64 'sys_log' => true,
65 'tx_extensionmanager_domain_model_extension' => true
66 ];
67
68 /**
69 * Definition of fields to exclude from ReferenceIndex in *every* table
70 *
71 * Implemented as array with fields as keys and booleans as values for fast isset() lookup instead of slow in_array()
72 *
73 * @var array
74 * @see getRelations()
75 * @see fetchTableRelationFields()
76 * @see shouldExcludeTableColumnFromReferenceIndex()
77 */
78 protected array $excludedColumns = [
79 'uid' => true,
80 'perms_userid' => true,
81 'perms_groupid' => true,
82 'perms_user' => true,
83 'perms_group' => true,
84 'perms_everybody' => true,
85 'pid' => true
86 ];
87
88 /**
89 * This array holds the FlexForm references of a record
90 *
91 * @var array
92 * @see getRelations()
93 * @see FlexFormTools::traverseFlexFormXMLData()
94 * @see getRelations_flexFormCallBack()
95 */
96 protected $temp_flexRelations = [];
97
98 /**
99 * An index of all found references of a single record
100 *
101 * @var array
102 */
103 protected $relations = [];
104
105 /**
106 * Number which we can increase if a change in the code means we will have to force a re-generation of the index.
107 *
108 * @var int
109 * @see updateRefIndexTable()
110 */
111 protected $hashVersion = 1;
112
113 /**
114 * Current workspace id
115 */
116 protected int $workspaceId = 0;
117
118 /**
119 * A list of fields that may contain relations per TCA table.
120 * This is either ['*'] or an array of single field names. The list
121 * depends on TCA and is built when a first table row is handled.
122 */
123 protected array $tableRelationFieldCache = [];
124
125 protected EventDispatcherInterface $eventDispatcher;
126
127 public function __construct(EventDispatcherInterface $eventDispatcher = null)
128 {
129 $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
130 }
131
132 /**
133 * Sets the current workspace id
134 *
135 * @param int $workspaceId
136 * @see updateIndex()
137 */
138 public function setWorkspaceId($workspaceId)
139 {
140 $this->workspaceId = (int)$workspaceId;
141 }
142
143 /**
144 * Gets the current workspace id
145 *
146 * @return int
147 * @see updateRefIndexTable()
148 */
149 protected function getWorkspaceId()
150 {
151 return $this->workspaceId;
152 }
153
154 /**
155 * Call this function to update the sys_refindex table for a record (even one just deleted)
156 * NOTICE: Currently, references updated for a deleted-flagged record will not include those from within FlexForm
157 * fields in some cases where the data structure is defined by another record since the resolving process ignores
158 * deleted records! This will also result in bad cleaning up in DataHandler I think... Anyway, that's the story of
159 * FlexForms; as long as the DS can change, lots of references can get lost in no time.
160 *
161 * @param string $tableName Table name
162 * @param int $uid UID of record
163 * @param bool $testOnly If set, nothing will be written to the index but the result value will still report statistics on what is added, deleted and kept. Can be used for mere analysis.
164 * @return array Array with statistics about how many index records were added, deleted and not altered plus the complete reference set for the record.
165 */
166 public function updateRefIndexTable($tableName, $uid, $testOnly = false)
167 {
168 $result = [
169 'keptNodes' => 0,
170 'deletedNodes' => 0,
171 'addedNodes' => 0
172 ];
173
174 $uid = $uid ? (int)$uid : 0;
175 if (!$uid) {
176 return $result;
177 }
178
179 // If this table cannot contain relations, skip it
180 if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
181 return $result;
182 }
183
184 $tableRelationFields = $this->fetchTableRelationFields($tableName);
185
186 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
187 $connection = $connectionPool->getConnectionForTable('sys_refindex');
188
189 // Get current index from Database with hash as index using $uidIndexField
190 // no restrictions are needed, since sys_refindex is not a TCA table
191 $queryBuilder = $connection->createQueryBuilder();
192 $queryBuilder->getRestrictions()->removeAll();
193 $queryResult = $queryBuilder->select('hash')->from('sys_refindex')->where(
194 $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)),
195 $queryBuilder->expr()->eq('recuid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
196 $queryBuilder->expr()->eq(
197 'workspace',
198 $queryBuilder->createNamedParameter($this->getWorkspaceId(), \PDO::PARAM_INT)
199 )
200 )->execute();
201 $currentRelationHashes = [];
202 while ($relation = $queryResult->fetch()) {
203 $currentRelationHashes[$relation['hash']] = true;
204 }
205
206 // If the table has fields which could contain relations and the record does exist
207 if ($tableRelationFields !== []) {
208 $existingRecord = $this->getRecord($tableName, $uid);
209 if ($existingRecord) {
210 // Table has relation fields and record exists - get relations
211 $this->relations = [];
212 $relations = $this->generateDataUsingRecord($tableName, $existingRecord);
213 if (!is_array($relations)) {
214 return $result;
215 }
216 // Traverse the generated index:
217 foreach ($relations as &$relation) {
218 if (!is_array($relation)) {
219 continue;
220 }
221 // Exclude any relations TO a specific table
222 if (($relation['ref_table'] ?? '') && $this->shouldExcludeTableFromReferenceIndex($relation['ref_table'])) {
223 continue;
224 }
225 $relation['hash'] = md5(implode('///', $relation) . '///' . $this->hashVersion);
226 // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
227 if (isset($currentRelationHashes[$relation['hash']])) {
228 unset($currentRelationHashes[$relation['hash']]);
229 $result['keptNodes']++;
230 $relation['_ACTION'] = 'KEPT';
231 } else {
232 // If new, add it:
233 if (!$testOnly) {
234 $connection->insert('sys_refindex', $relation);
235 }
236 $result['addedNodes']++;
237 $relation['_ACTION'] = 'ADDED';
238 }
239 }
240 $result['relations'] = $relations;
241 }
242 }
243
244 // If any old are left, remove them:
245 if (!empty($currentRelationHashes)) {
246 $hashList = array_keys($currentRelationHashes);
247 if (!empty($hashList)) {
248 $result['deletedNodes'] = count($hashList);
249 $result['deletedNodes_hashList'] = implode(',', $hashList);
250 if (!$testOnly) {
251 $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
252 foreach (array_chunk($hashList, $maxBindParameters - 10, true) as $chunk) {
253 if (empty($chunk)) {
254 continue;
255 }
256 $queryBuilder = $connection->createQueryBuilder();
257 $queryBuilder
258 ->delete('sys_refindex')
259 ->where(
260 $queryBuilder->expr()->in(
261 'hash',
262 $queryBuilder->createNamedParameter($chunk, Connection::PARAM_STR_ARRAY)
263 )
264 )
265 ->execute();
266 }
267 }
268 }
269 }
270
271 return $result;
272 }
273
274 /**
275 * Returns the amount of references for the given record
276 *
277 * @param string $tableName
278 * @param int $uid
279 * @return int
280 */
281 public function getNumberOfReferencedRecords(string $tableName, int $uid): int
282 {
283 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
284 return (int)$queryBuilder
285 ->count('*')->from('sys_refindex')
286 ->where(
287 $queryBuilder->expr()->eq(
288 'ref_table',
289 $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
290 ),
291 $queryBuilder->expr()->eq(
292 'ref_uid',
293 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
294 )
295 )->execute()->fetchColumn(0);
296 }
297
298 /**
299 * Calculate the relations for a record of a given table
300 *
301 * @param string $tableName Table being processed
302 * @param array $record Record from $tableName
303 * @return array
304 */
305 protected function generateDataUsingRecord(string $tableName, array $record): array
306 {
307 if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
308 // Never write relations for t3ver_state = 2 delete placeholder records
309 $versionState = VersionState::cast($record['t3ver_state'] ?? 0);
310 if ($versionState->equals(VersionState::DELETE_PLACEHOLDER)) {
311 return [];
312 }
313 }
314
315 $this->relations = [];
316 // Get all relations from record:
317 $recordRelations = $this->getRelations($tableName, $record);
318 // Traverse those relations, compile records to insert in table:
319 foreach ($recordRelations as $fieldName => $fieldRelations) {
320 // Based on type
321 switch ((string)$fieldRelations['type']) {
322 case 'db':
323 $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, '', $fieldRelations['itemArray']);
324 break;
325 case 'flex':
326 // DB references in FlexForms
327 if (is_array($fieldRelations['flexFormRels']['db'])) {
328 foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
329 $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, $flexPointer, $subList);
330 }
331 }
332 // Soft references in FlexForms
333 // @todo #65464 Test correct handling of soft references in FlexForms
334 if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
335 foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
336 $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, $flexPointer, $subList['keys']);
337 }
338 }
339 break;
340 }
341 // Soft references in the field
342 if (is_array($fieldRelations['softrefs'])) {
343 $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, '', $fieldRelations['softrefs']['keys']);
344 }
345 }
346
347 return array_filter($this->relations);
348 }
349
350 /**
351 * Create array with field/value pairs ready to insert in database
352 *
353 * @param string $tableName Tablename of source record (where reference is located)
354 * @param array $record Record from $table
355 * @param string $fieldName Fieldname of source record (where reference is located)
356 * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [$field]
357 * @param string $referencedTable In database references the tablename the reference points to. Keyword "_STRING" indicates special usage (typ. SoftReference) in $referenceString
358 * @param int $referencedUid In database references the UID of the record (zero $referencedTable is "_STRING")
359 * @param string $referenceString For "_STRING" references: The string.
360 * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
361 * @param string $softReferenceKey If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
362 * @param string $softReferenceId Soft reference ID for key. Might be useful for replace operations.
363 * @return array|bool Array to insert in DB or false if record should not be processed
364 */
365 protected function createEntryDataUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, string $referencedTable, int $referencedUid, string $referenceString = '', int $sort = -1, string $softReferenceKey = '', string $softReferenceId = '')
366 {
367 $workspaceId = 0;
368 if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
369 $workspaceId = $this->getWorkspaceId();
370 if (isset($record['t3ver_wsid']) && (int)$record['t3ver_wsid'] !== $workspaceId) {
371 // The given record is workspace-enabled but doesn't live in the selected workspace => don't add index as it's not actually there
372 return false;
373 }
374 }
375 return [
376 'tablename' => $tableName,
377 'recuid' => $record['uid'],
378 'field' => $fieldName,
379 'flexpointer' => $flexPointer,
380 'softref_key' => $softReferenceKey,
381 'softref_id' => $softReferenceId,
382 'sorting' => $sort,
383 'workspace' => $workspaceId,
384 'ref_table' => $referencedTable,
385 'ref_uid' => $referencedUid,
386 'ref_string' => mb_substr($referenceString, 0, 1024)
387 ];
388 }
389
390 /**
391 * Add database references to ->relations array based on fetched record
392 *
393 * @param string $tableName Tablename of source record (where reference is located)
394 * @param array $record Record from $tableName
395 * @param string $fieldName Fieldname of source record (where reference is located)
396 * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
397 * @param array $items Data array with database relations (table/id)
398 */
399 protected function createEntryDataForDatabaseRelationsUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, array $items)
400 {
401 foreach ($items as $sort => $i) {
402 $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $i['table'], (int)$i['id'], '', $sort);
403 }
404 }
405
406 /**
407 * Add SoftReference references to ->relations array based on fetched record
408 *
409 * @param string $tableName Tablename of source record (where reference is located)
410 * @param array $record Record from $tableName
411 * @param string $fieldName Fieldname of source record (where reference is located)
412 * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
413 * @param array $keys Data array with soft reference keys
414 */
415 protected function createEntryDataForSoftReferencesUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, array $keys)
416 {
417 foreach ($keys as $spKey => $elements) {
418 if (is_array($elements)) {
419 foreach ($elements as $subKey => $el) {
420 if (is_array($el['subst'])) {
421 switch ((string)$el['subst']['type']) {
422 case 'db':
423 [$referencedTable, $referencedUid] = explode(':', $el['subst']['recordRef']);
424 $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $referencedTable, (int)$referencedUid, '', -1, $spKey, $subKey);
425 break;
426 case 'string':
427 $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
428 break;
429 }
430 }
431 }
432 }
433 }
434 }
435
436 /*******************************
437 *
438 * Get relations from table row
439 *
440 *******************************/
441
442 /**
443 * Returns relation information for a $table/$row-array
444 * Traverses all fields in input row which are configured in TCA/columns
445 * It looks for hard relations to records in the TCA types "select" and "group"
446 *
447 * @param string $table Table name
448 * @param array $row Row from table
449 * @param string $onlyField Specific field to fetch for.
450 * @return array Array with information about relations
451 * @see export_addRecord()
452 */
453 public function getRelations($table, $row, $onlyField = '')
454 {
455 // Initialize:
456 $uid = $row['uid'];
457 $outRow = [];
458 foreach ($row as $field => $value) {
459 if ($this->shouldExcludeTableColumnFromReferenceIndex($table, $field, $onlyField) === false) {
460 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
461 // Add a softref definition for link fields if the TCA does not specify one already
462 if ($conf['type'] === 'input' && $conf['renderType'] === 'inputLink' && empty($conf['softref'])) {
463 $conf['softref'] = 'typolink';
464 }
465 // Add DB:
466 $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table);
467 if (!empty($resultsFromDatabase)) {
468 // Create an entry for the field with all DB relations:
469 $outRow[$field] = [
470 'type' => 'db',
471 'itemArray' => $resultsFromDatabase
472 ];
473 }
474 // For "flex" fieldtypes we need to traverse the structure looking for db references of course!
475 if ($conf['type'] === 'flex') {
476 // Get current value array:
477 // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up
478 // the note in the JavaDoc documentation for the function FlexFormTools->getDataStructureIdentifier()
479 $currentValueArray = GeneralUtility::xml2array($value);
480 // Traversing the XML structure, processing:
481 if (is_array($currentValueArray)) {
482 $this->temp_flexRelations = [
483 'db' => [],
484 'softrefs' => []
485 ];
486 // Create and call iterator object:
487 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
488 $flexFormTools->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
489 // Create an entry for the field:
490 $outRow[$field] = [
491 'type' => 'flex',
492 'flexFormRels' => $this->temp_flexRelations
493 ];
494 }
495 }
496 // Soft References:
497 if ((string)$value !== '') {
498 $softRefValue = $value;
499 if (!empty($conf['softref'])) {
500 $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
501 foreach ($softRefs as $spKey => $spParams) {
502 $softRefObj = BackendUtility::softRefParserObj($spKey);
503 if (is_object($softRefObj)) {
504 $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);
505 if (is_array($resultArray)) {
506 $outRow[$field]['softrefs']['keys'][$spKey] = $resultArray['elements'];
507 if ((string)$resultArray['content'] !== '') {
508 $softRefValue = $resultArray['content'];
509 }
510 }
511 }
512 }
513 }
514 if (!empty($outRow[$field]['softrefs']) && (string)$value !== (string)$softRefValue && strpos($softRefValue, '{softref:') !== false) {
515 $outRow[$field]['softrefs']['tokenizedContent'] = $softRefValue;
516 }
517 }
518 }
519 }
520 return $outRow;
521 }
522
523 /**
524 * Callback function for traversing the FlexForm structure in relation to finding DB references!
525 *
526 * @param array $dsArr Data structure for the current value
527 * @param mixed $dataValue Current value
528 * @param array $PA Additional configuration used in calling function
529 * @param string $structurePath Path of value in DS structure
530 * @see DataHandler::checkValue_flex_procInData_travDS()
531 * @see FlexFormTools::traverseFlexFormXMLData()
532 */
533 public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath)
534 {
535 // Removing "data/" in the beginning of path (which points to location in data array)
536 $structurePath = substr($structurePath, 5) . '/';
537 $dsConf = $dsArr['TCEforms']['config'];
538 // Implode parameter values:
539 [$table, $uid, $field] = [
540 $PA['table'],
541 $PA['uid'],
542 $PA['field']
543 ];
544 // Add a softref definition for link fields if the TCA does not specify one already
545 if ($dsConf['type'] === 'input' && $dsConf['renderType'] === 'inputLink' && empty($dsConf['softref'])) {
546 $dsConf['softref'] = 'typolink';
547 }
548 // Add DB:
549 $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table);
550 if (!empty($resultsFromDatabase)) {
551 // Create an entry for the field with all DB relations:
552 $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
553 }
554 // Soft References:
555 if (is_array($dataValue) || (string)$dataValue !== '') {
556 $softRefValue = $dataValue;
557 $softRefs = BackendUtility::explodeSoftRefParserList($dsConf['softref']);
558 if ($softRefs !== false) {
559 foreach ($softRefs as $spKey => $spParams) {
560 $softRefObj = BackendUtility::softRefParserObj($spKey);
561 if (is_object($softRefObj)) {
562 $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams, $structurePath);
563 if (is_array($resultArray) && is_array($resultArray['elements'])) {
564 $this->temp_flexRelations['softrefs'][$structurePath]['keys'][$spKey] = $resultArray['elements'];
565 if ((string)$resultArray['content'] !== '') {
566 $softRefValue = $resultArray['content'];
567 }
568 }
569 }
570 }
571 }
572 if (!empty($this->temp_flexRelations['softrefs']) && (string)$dataValue !== (string)$softRefValue) {
573 $this->temp_flexRelations['softrefs'][$structurePath]['tokenizedContent'] = $softRefValue;
574 }
575 }
576 }
577
578 /**
579 * Check field configuration if it is a DB relation field and extract DB relations if any
580 *
581 * @param string $value Field value
582 * @param array $conf Field configuration array of type "TCA/columns
583 * @param int $uid Field uid
584 * @param string $table Table name
585 * @return array|bool If field type is OK it will return an array with the database relations. Else FALSE
586 */
587 protected function getRelations_procDB($value, $conf, $uid, $table = '')
588 {
589 // Get IRRE relations
590 if (empty($conf)) {
591 return false;
592 }
593 if ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
594 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
595 $dbAnalysis->setUseLiveReferenceIds(false);
596 $dbAnalysis->setWorkspaceId($this->getWorkspaceId());
597 $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
598 return $dbAnalysis->itemArray;
599 // DB record lists:
600 }
601 if ($this->isDbReferenceField($conf)) {
602 $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
603 if ($conf['MM_opposite_field']) {
604 return [];
605 }
606 $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
607 $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
608 return $dbAnalysis->itemArray;
609 }
610 return false;
611 }
612
613 /*******************************
614 *
615 * Setting values
616 *
617 *******************************/
618
619 /**
620 * Setting the value of a reference or removing it completely.
621 * Usage: For lowlevel clean up operations!
622 * WARNING: With this you can set values that are not allowed in the database since it will bypass all checks for validity!
623 * Hence it is targeted at clean-up operations. Please use DataHandler in the usual ways if you wish to manipulate references.
624 * Since this interface allows updates to soft reference values (which DataHandler does not directly) you may like to use it
625 * for that as an exception to the warning above.
626 * Notice; If you want to remove multiple references from the same field, you MUST start with the one having the highest
627 * sorting number. If you don't the removal of a reference with a lower number will recreate an index in which the remaining
628 * references in that field has new hash-keys due to new sorting numbers - and you will get errors for the remaining operations
629 * which cannot find the hash you feed it!
630 * To ensure proper working only admin-BE_USERS in live workspace should use this function
631 *
632 * @param string $hash 32-byte hash string identifying the record from sys_refindex which you wish to change the value for
633 * @param mixed $newValue Value you wish to set for reference. If NULL, the reference is removed (unless a soft-reference in which case it can only be set to a blank string). If you wish to set a database reference, use the format "[table]:[uid]". Any other case, the input value is set as-is
634 * @param bool $returnDataArray Return $dataArray only, do not submit it to database.
635 * @param bool $bypassWorkspaceAdminCheck If set, it will bypass check for workspace-zero and admin user
636 * @return string|bool|array FALSE (=OK), error message string or array (if $returnDataArray is set!)
637 */
638 public function setReferenceValue($hash, $newValue, $returnDataArray = false, $bypassWorkspaceAdminCheck = false)
639 {
640 $backendUser = $this->getBackendUser();
641 if ($backendUser->workspace === 0 && $backendUser->isAdmin() || $bypassWorkspaceAdminCheck) {
642 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
643 $queryBuilder->getRestrictions()->removeAll();
644
645 // Get current index from Database
646 $referenceRecord = $queryBuilder
647 ->select('*')
648 ->from('sys_refindex')
649 ->where(
650 $queryBuilder->expr()->eq('hash', $queryBuilder->createNamedParameter($hash, \PDO::PARAM_STR))
651 )
652 ->setMaxResults(1)
653 ->execute()
654 ->fetch();
655
656 // Check if reference existed.
657 if (!is_array($referenceRecord)) {
658 return 'ERROR: No reference record with hash="' . $hash . '" was found!';
659 }
660
661 if (empty($GLOBALS['TCA'][$referenceRecord['tablename']])) {
662 return 'ERROR: Table "' . $referenceRecord['tablename'] . '" was not in TCA!';
663 }
664
665 // Get that record from database
666 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
667 ->getQueryBuilderForTable($referenceRecord['tablename']);
668 $queryBuilder->getRestrictions()->removeAll();
669 $record = $queryBuilder
670 ->select('*')
671 ->from($referenceRecord['tablename'])
672 ->where(
673 $queryBuilder->expr()->eq(
674 'uid',
675 $queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
676 )
677 )
678 ->setMaxResults(1)
679 ->execute()
680 ->fetch();
681
682 if (is_array($record)) {
683 // Get relation for single field from record
684 $recordRelations = $this->getRelations($referenceRecord['tablename'], $record, $referenceRecord['field']);
685 if ($fieldRelation = $recordRelations[$referenceRecord['field']]) {
686 // Initialize data array that is to be sent to DataHandler afterwards:
687 $dataArray = [];
688 // Based on type
689 switch ((string)$fieldRelation['type']) {
690 case 'db':
691 $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['itemArray'], $newValue, $dataArray);
692 if ($error) {
693 return $error;
694 }
695 break;
696 case 'flex':
697 // DB references in FlexForms
698 if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
699 $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
700 if ($error) {
701 return $error;
702 }
703 }
704 // Soft references in FlexForms
705 if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
706 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
707 if ($error) {
708 return $error;
709 }
710 }
711 break;
712 }
713 // Soft references in the field:
714 if ($referenceRecord['softref_key'] && is_array($fieldRelation['softrefs']['keys'][$referenceRecord['softref_key']])) {
715 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['softrefs'], $newValue, $dataArray);
716 if ($error) {
717 return $error;
718 }
719 }
720 // Data Array, now ready to be sent to DataHandler
721 if ($returnDataArray) {
722 return $dataArray;
723 }
724 // Execute CMD array:
725 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
726 $dataHandler->dontProcessTransformations = true;
727 $dataHandler->bypassWorkspaceRestrictions = true;
728 // Otherwise this may lead to permission issues if user is not admin
729 $dataHandler->bypassAccessCheckForRecords = true;
730 // Check has been done previously that there is a backend user which is Admin and also in live workspace
731 $dataHandler->start($dataArray, []);
732 $dataHandler->process_datamap();
733 // Return errors if any:
734 if (!empty($dataHandler->errorLog)) {
735 return LF . 'DataHandler:' . implode(LF . 'DataHandler:', $dataHandler->errorLog);
736 }
737 }
738 }
739 } else {
740 return 'ERROR: BE_USER object is not admin OR not in workspace 0 (Live)';
741 }
742
743 return false;
744 }
745
746 /**
747 * Setting a value for a reference for a DB field:
748 *
749 * @param array $refRec sys_refindex record
750 * @param array $itemArray Array of references from that field
751 * @param string $newValue Value to substitute current value with (or NULL to unset it)
752 * @param array $dataArray Data array in which the new value is set (passed by reference)
753 * @param string $flexPointer Flexform pointer, if in a flex form field.
754 * @return string Error message if any, otherwise FALSE = OK
755 */
756 protected function setReferenceValue_dbRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
757 {
758 if ((int)$itemArray[$refRec['sorting']]['id'] === (int)$refRec['ref_uid'] && (string)$itemArray[$refRec['sorting']]['table'] === (string)$refRec['ref_table']) {
759 // Setting or removing value:
760 // Remove value:
761 if ($newValue === null) {
762 unset($itemArray[$refRec['sorting']]);
763 } else {
764 [$itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']] = explode(':', $newValue);
765 }
766 // Traverse and compile new list of records:
767 $saveValue = [];
768 foreach ($itemArray as $pair) {
769 $saveValue[] = $pair['table'] . '_' . $pair['id'];
770 }
771 // Set in data array:
772 if ($flexPointer) {
773 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
774 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
775 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
776 } else {
777 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
778 }
779 } else {
780 return 'ERROR: table:id pair "' . $refRec['ref_table'] . ':' . $refRec['ref_uid'] . '" did not match that of the record ("' . $itemArray[$refRec['sorting']]['table'] . ':' . $itemArray[$refRec['sorting']]['id'] . '") in sorting index "' . $refRec['sorting'] . '"';
781 }
782
783 return false;
784 }
785
786 /**
787 * Setting a value for a soft reference token
788 *
789 * @param array $refRec sys_refindex record
790 * @param array $softref Array of soft reference occurrences
791 * @param string $newValue Value to substitute current value with
792 * @param array $dataArray Data array in which the new value is set (passed by reference)
793 * @param string $flexPointer Flexform pointer, if in a flex form field.
794 * @return string Error message if any, otherwise FALSE = OK
795 */
796 protected function setReferenceValue_softreferences($refRec, $softref, $newValue, &$dataArray, $flexPointer = '')
797 {
798 if (!is_array($softref['keys'][$refRec['softref_key']][$refRec['softref_id']])) {
799 return 'ERROR: Soft reference parser key "' . $refRec['softref_key'] . '" or the index "' . $refRec['softref_id'] . '" was not found.';
800 }
801
802 // Set new value:
803 $softref['keys'][$refRec['softref_key']][$refRec['softref_id']]['subst']['tokenValue'] = '' . $newValue;
804 // Traverse softreferences and replace in tokenized content to rebuild it with new value inside:
805 foreach ($softref['keys'] as $sfIndexes) {
806 foreach ($sfIndexes as $data) {
807 $softref['tokenizedContent'] = str_replace('{softref:' . $data['subst']['tokenID'] . '}', $data['subst']['tokenValue'], $softref['tokenizedContent']);
808 }
809 }
810 // Set in data array:
811 if (strpos($softref['tokenizedContent'], '{softref:') === false) {
812 if ($flexPointer) {
813 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
814 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
815 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
816 } else {
817 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
818 }
819 } else {
820 return 'ERROR: After substituting all found soft references there were still soft reference tokens in the text. (theoretically this does not have to be an error if the string "{softref:" happens to be in the field for another reason.)';
821 }
822
823 return false;
824 }
825
826 /*******************************
827 *
828 * Helper functions
829 *
830 *******************************/
831
832 /**
833 * Returns TRUE if the TCA/columns field type is a DB reference field
834 *
835 * @param array $configuration Config array for TCA/columns field
836 * @return bool TRUE if DB reference field (group/db or select with foreign-table)
837 */
838 protected function isDbReferenceField(array $configuration): bool
839 {
840 return
841 ($configuration['type'] === 'group' && $configuration['internal_type'] === 'db')
842 || (
843 ($configuration['type'] === 'select' || $configuration['type'] === 'inline')
844 && !empty($configuration['foreign_table'])
845 )
846 ;
847 }
848
849 /**
850 * Returns TRUE if the TCA/columns field type is a reference field
851 *
852 * @param array $configuration Config array for TCA/columns field
853 * @return bool TRUE if reference field
854 */
855 protected function isReferenceField(array $configuration): bool
856 {
857 return
858 $this->isDbReferenceField($configuration)
859 ||
860 ($configuration['type'] === 'input' && $configuration['renderType'] === 'inputLink') // getRelations_procDB
861 ||
862 $configuration['type'] === 'flex'
863 ||
864 isset($configuration['softref'])
865 ;
866 }
867
868 /**
869 * Returns all fields of a table which could contain a relation
870 *
871 * @param string $tableName Name of the table
872 * @return array Fields which may contain relations
873 */
874 protected function fetchTableRelationFields(string $tableName): array
875 {
876 if (!empty($this->tableRelationFieldCache[$tableName])) {
877 return $this->tableRelationFieldCache[$tableName];
878 }
879 if (!isset($GLOBALS['TCA'][$tableName]['columns'])) {
880 return [];
881 }
882 $fields = [];
883 foreach ($GLOBALS['TCA'][$tableName]['columns'] as $field => $fieldDefinition) {
884 if (is_array($fieldDefinition['config'])) {
885 // Check for flex field
886 if (isset($fieldDefinition['config']['type']) && $fieldDefinition['config']['type'] === 'flex') {
887 // Fetch all fields if the is a field of type flex in the table definition because the complete row is passed to
888 // FlexFormTools->getDataStructureIdentifier() in the end and might be needed in ds_pointerField or a hook
889 $this->tableRelationFieldCache[$tableName] = ['*'];
890 return ['*'];
891 }
892 // Only fetch this field if it can contain a reference
893 if ($this->isReferenceField($fieldDefinition['config'])) {
894 $fields[] = $field;
895 }
896 }
897 }
898 $this->tableRelationFieldCache[$tableName] = $fields;
899 return $fields;
900 }
901
902 /**
903 * Updating Index (External API)
904 *
905 * @param bool $testOnly If set, only a test
906 * @param ProgressListenerInterface|null $progressListener If set, the current progress is added to the listener
907 * @return array Header and body status content
908 */
909 public function updateIndex($testOnly, ?ProgressListenerInterface $progressListener = null)
910 {
911 $errors = [];
912 $tableNames = [];
913 $recCount = 0;
914 // Traverse all tables:
915 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
916 $refIndexConnectionName = empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'])
917 ? ConnectionPool::DEFAULT_CONNECTION_NAME
918 : $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'];
919
920 foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
921 if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
922 continue;
923 }
924 $tableConnectionName = empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])
925 ? ConnectionPool::DEFAULT_CONNECTION_NAME
926 : $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
927
928 $fields = ['uid'];
929 if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
930 $fields[] = 't3ver_wsid';
931 }
932 // Traverse all records in tables, including deleted records
933 $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
934 $queryBuilder->getRestrictions()->removeAll();
935 try {
936 $queryResult = $queryBuilder
937 ->select(...$fields)
938 ->from($tableName)
939 ->orderBy('uid')
940 ->execute();
941 } catch (DBALException $e) {
942 // Table exists in TCA but does not exist in the database
943 $msg = 'Table "' .
944 $tableName .
945 '" exists in TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.';
946 $this->logger->error($msg, ['exception' => $e]);
947 continue;
948 }
949
950 if ($progressListener) {
951 $progressListener->start($queryResult->rowCount(), $tableName);
952 }
953 $tableNames[] = $tableName;
954 while ($record = $queryResult->fetch()) {
955 if ($progressListener) {
956 $progressListener->advance();
957 }
958 /** @var ReferenceIndex $refIndexObj */
959 $refIndexObj = GeneralUtility::makeInstance(self::class);
960 if (isset($record['t3ver_wsid'])) {
961 $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
962 }
963 $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
964 $recCount++;
965 if ($result['addedNodes'] || $result['deletedNodes']) {
966 $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
967 $errors[] = $error;
968 if ($progressListener) {
969 $progressListener->log($error, LogLevel::WARNING);
970 }
971 }
972 }
973 if ($progressListener) {
974 $progressListener->finish();
975 }
976
977 // Subselect based queries only work on the same connection
978 if ($refIndexConnectionName !== $tableConnectionName) {
979 $this->logger->error('Not checking table "' . $tableName . '" for lost indexes, "sys_refindex" table uses a different connection');
980 continue;
981 }
982
983 // Searching for lost indexes for this table
984 // Build sub-query to find lost records
985 $subQueryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
986 $subQueryBuilder->getRestrictions()->removeAll();
987 $subQueryBuilder
988 ->select('uid')
989 ->from($tableName, 'sub_' . $tableName)
990 ->where(
991 $subQueryBuilder->expr()->eq(
992 'sub_' . $tableName . '.uid',
993 $queryBuilder->quoteIdentifier('sys_refindex.recuid')
994 )
995 );
996
997 // Main query to find lost records
998 $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
999 $queryBuilder->getRestrictions()->removeAll();
1000 $lostIndexes = $queryBuilder
1001 ->count('hash')
1002 ->from('sys_refindex')
1003 ->where(
1004 $queryBuilder->expr()->eq(
1005 'tablename',
1006 $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
1007 ),
1008 'NOT EXISTS (' . $subQueryBuilder->getSQL() . ')'
1009 )
1010 ->execute()
1011 ->fetchColumn(0);
1012
1013 if ($lostIndexes > 0) {
1014 $error = 'Table ' . $tableName . ' has ' . $lostIndexes . ' lost indexes which are now deleted';
1015 $errors[] = $error;
1016 if ($progressListener) {
1017 $progressListener->log($error, LogLevel::WARNING);
1018 }
1019 if (!$testOnly) {
1020 $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1021 $queryBuilder->delete('sys_refindex')
1022 ->where(
1023 $queryBuilder->expr()->eq(
1024 'tablename',
1025 $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
1026 ),
1027 'NOT EXISTS (' . $subQueryBuilder->getSQL() . ')'
1028 )
1029 ->execute();
1030 }
1031 }
1032 }
1033
1034 // Searching lost indexes for non-existing tables
1035 $lostTables = $this->getAmountOfUnusedTablesInReferenceIndex($tableNames);
1036 if ($lostTables > 0) {
1037 $error = 'Index table hosted ' . $lostTables . ' indexes for non-existing tables, now removed';
1038 $errors[] = $error;
1039 if ($progressListener) {
1040 $progressListener->log($error, LogLevel::WARNING);
1041 }
1042 if (!$testOnly) {
1043 $this->removeReferenceIndexDataFromUnusedDatabaseTables($tableNames);
1044 }
1045 }
1046 $errorCount = count($errors);
1047 $recordsCheckedString = $recCount . ' records from ' . count($tableNames) . ' tables were checked/updated.';
1048 if ($progressListener) {
1049 if ($errorCount) {
1050 $progressListener->log($recordsCheckedString . 'Updates: ' . $errorCount, LogLevel::WARNING);
1051 } else {
1052 $progressListener->log($recordsCheckedString . 'Index Integrity was perfect!', LogLevel::INFO);
1053 }
1054 }
1055 if (!$testOnly) {
1056 $registry = GeneralUtility::makeInstance(Registry::class);
1057 $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
1058 }
1059 return ['resultText' => trim($recordsCheckedString), 'errors' => $errors];
1060 }
1061
1062 protected function getAmountOfUnusedTablesInReferenceIndex(array $tableNames): int
1063 {
1064 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1065 $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1066 $queryBuilder->getRestrictions()->removeAll();
1067 $lostTables = $queryBuilder
1068 ->count('hash')
1069 ->from('sys_refindex')
1070 ->where(
1071 $queryBuilder->expr()->notIn(
1072 'tablename',
1073 $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
1074 )
1075 )->execute()
1076 ->fetchColumn(0);
1077 return (int)$lostTables;
1078 }
1079
1080 protected function removeReferenceIndexDataFromUnusedDatabaseTables(array $tableNames): void
1081 {
1082 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1083 $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1084 $queryBuilder->delete('sys_refindex')
1085 ->where(
1086 $queryBuilder->expr()->notIn(
1087 'tablename',
1088 $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
1089 )
1090 )->execute();
1091 }
1092
1093 /**
1094 * Get one record from database.
1095 *
1096 * @param string $tableName
1097 * @param int $uid
1098 * @return array|false
1099 */
1100 protected function getRecord(string $tableName, int $uid)
1101 {
1102 // Fetch fields of the table which might contain relations
1103 $tableRelationFields = $this->fetchTableRelationFields($tableName);
1104
1105 if ($tableRelationFields === []) {
1106 // Return if there are no fields which could contain relations
1107 return $this->relations;
1108 }
1109 if ($tableRelationFields !== ['*']) {
1110 // Only fields that might contain relations are fetched
1111 $tableRelationFields[] = 'uid';
1112 if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
1113 $tableRelationFields = array_merge($tableRelationFields, ['t3ver_wsid', 't3ver_state']);
1114 }
1115 }
1116
1117 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1118 ->getQueryBuilderForTable($tableName);
1119 $queryBuilder->getRestrictions()->removeAll();
1120 $queryBuilder
1121 ->select(...array_unique($tableRelationFields))
1122 ->from($tableName)
1123 ->where(
1124 $queryBuilder->expr()->eq(
1125 'uid',
1126 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
1127 )
1128 );
1129 // Do not fetch soft deleted records
1130 $deleteField = (string)($GLOBALS['TCA'][$tableName]['ctrl']['delete'] ?? '');
1131 if ($deleteField !== '') {
1132 $queryBuilder->andWhere(
1133 $queryBuilder->expr()->eq(
1134 $deleteField,
1135 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1136 )
1137 );
1138 }
1139 return $queryBuilder->execute()->fetch();
1140 }
1141
1142 /**
1143 * Checks if a given table should be excluded from ReferenceIndex
1144 *
1145 * @param string $tableName Name of the table
1146 * @return bool true if it should be excluded
1147 */
1148 protected function shouldExcludeTableFromReferenceIndex(string $tableName): bool
1149 {
1150 if (isset($this->excludedTables[$tableName])) {
1151 return $this->excludedTables[$tableName];
1152 }
1153 // Only exclude tables from ReferenceIndex which do not contain any relations and never
1154 // did since existing references won't be deleted!
1155 $event = new IsTableExcludedFromReferenceIndexEvent($tableName);
1156 $event = $this->eventDispatcher->dispatch($event);
1157 $this->excludedTables[$tableName] = $event->isTableExcluded();
1158 return $this->excludedTables[$tableName];
1159 }
1160
1161 /**
1162 * Checks if a given column in a given table should be excluded in the ReferenceIndex process
1163 *
1164 * @param string $tableName Name of the table
1165 * @param string $column Name of the column
1166 * @param string $onlyColumn Name of a specific column to fetch
1167 * @return bool true if it should be excluded
1168 */
1169 protected function shouldExcludeTableColumnFromReferenceIndex(
1170 string $tableName,
1171 string $column,
1172 string $onlyColumn
1173 ): bool {
1174 if (isset($this->excludedColumns[$column])) {
1175 return true;
1176 }
1177 if (is_array($GLOBALS['TCA'][$tableName]['columns'][$column]) && (!$onlyColumn || $onlyColumn === $column)) {
1178 return false;
1179 }
1180 return true;
1181 }
1182
1183 /**
1184 * Enables the runtime-based caches
1185 * Could lead to side effects, depending if the reference index instance is run multiple times
1186 * while records would be changed.
1187 *
1188 * @deprecated since v11, will be removed in v12.
1189 */
1190 public function enableRuntimeCache()
1191 {
1192 trigger_error('Calling ReferenceIndex->enableRuntimeCache() is obsolete and should be dropped.', E_USER_DEPRECATED);
1193 }
1194
1195 /**
1196 * Disables the runtime-based cache
1197 *
1198 * @deprecated since v11, will be removed in v12.
1199 */
1200 public function disableRuntimeCache()
1201 {
1202 trigger_error('Calling ReferenceIndex->disableRuntimeCache() is obsolete and should be dropped.', E_USER_DEPRECATED);
1203 }
1204
1205 /**
1206 * Returns the current BE user.
1207 *
1208 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1209 */
1210 protected function getBackendUser()
1211 {
1212 return $GLOBALS['BE_USER'];
1213 }
1214 }