‪TYPO3CMS  ‪main
TcaFiles.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
25 use TYPO3\CMS\Backend\Utility\BackendUtility;
32 
37 {
38  private const ‪FILE_REFERENCE_TABLE = 'sys_file_reference';
39  private const ‪FOREIGN_SELECTOR = 'uid_local';
40 
41  public function ‪addData(array $result): array
42  {
43  // inlineFirstPid is currently resolved by TcaInline
44  // @todo check if duplicating the functionality makes sense to resolve dependencies
45 
46  foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
47  if (($fieldConfig['config']['type'] ?? '') !== 'file') {
48  continue;
49  }
50 
51  if (!$this->‪getBackendUser()->check('tables_modify', self::FILE_REFERENCE_TABLE)) {
52  // Early return if user is not allowed to modify the file reference table
53  continue;
54  }
55 
56  if (!(‪$GLOBALS['TCA'][self::FILE_REFERENCE_TABLE] ?? false)) {
57  throw new \RuntimeException('Table ' . self::FILE_REFERENCE_TABLE . ' does not exists', 1664364262);
58  }
59 
60  $childConfiguration = ‪$GLOBALS['TCA'][‪self::FILE_REFERENCE_TABLE]['columns'][‪self::FOREIGN_SELECTOR]['config'] ?? [];
61  if (($childConfiguration['type'] ?? '') !== 'group' || !($childConfiguration['allowed'] ?? false)) {
62  throw new \UnexpectedValueException(
63  'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points to field '
64  . self::FOREIGN_SELECTOR . ' of table ' . self::FILE_REFERENCE_TABLE . ', but this field '
65  . 'is either not defined, is not of type "group" or does not define the "allowed" option.',
66  1664364263
67  );
68  }
69 
70  $result['processedTca']['columns'][$fieldName]['children'] = [];
71 
72  $result = $this->‪initializeMinMaxItems($result, $fieldName);
73  $result = $this->‪initializeParentSysLanguageUid($result, $fieldName);
74  $result = $this->‪initializeAppearance($result, $fieldName);
75 
76  // If field is set to readOnly, set all fields of the relation to readOnly as well
77  if ($result['inlineParentConfig']['readOnly'] ?? false) {
78  foreach ($result['processedTca']['columns'] as $columnName => $columnConfiguration) {
79  $result['processedTca']['columns'][$columnName]['config']['readOnly'] = true;
80  }
81  }
82 
83  // Resolve existing file references - this is usually always done except on ajax calls
84  if ($result['inlineResolveExistingChildren']) {
85  $result = $this->‪resolveFileReferences($result, $fieldName);
86  if (!empty($fieldConfig['config']['selectorOrUniqueConfiguration'])) {
87  throw new \RuntimeException('selectorOrUniqueConfiguration not implemented for TCA type "file"', 1664380909);
88  }
89  }
90  }
91 
92  return $result;
93  }
94 
95  protected function ‪initializeMinMaxItems(array $result, string $fieldName): array
96  {
97  $config = $result['processedTca']['columns'][$fieldName]['config'];
98  $config['minitems'] = isset($config['minitems']) ? ‪MathUtility::forceIntegerInRange($config['minitems'], 0) : 0;
99  $config['maxitems'] = isset($config['maxitems']) ? ‪MathUtility::forceIntegerInRange($config['maxitems'], 1) : 99999;
100  $result['processedTca']['columns'][$fieldName]['config'] = $config;
101 
102  return $result;
103  }
104 
105  protected function ‪initializeParentSysLanguageUid(array $result, string $fieldName): array
106  {
107  if (($parentLanguageFieldName = (string)($result['processedTca']['ctrl']['languageField'] ?? '')) === ''
108  || !(‪$GLOBALS['TCA'][self::FILE_REFERENCE_TABLE]['ctrl']['languageField'] ?? false)
109  || isset($result['processedTca']['columns'][$fieldName]['config']['inline']['parentSysLanguageUid'])
110  || !isset($result['databaseRow'][$parentLanguageFieldName])
111  ) {
112  return $result;
113  }
114 
115  $result['processedTca']['columns'][$fieldName]['config']['inline']['parentSysLanguageUid'] =
116  is_array($result['databaseRow'][$parentLanguageFieldName])
117  ? (int)($result['databaseRow'][$parentLanguageFieldName][0] ?? 0)
118  : (int)$result['databaseRow'][$parentLanguageFieldName];
119 
120  return $result;
121  }
122 
123  protected function ‪initializeAppearance(array $result, string $fieldName): array
124  {
125  $result['processedTca']['columns'][$fieldName]['config']['appearance'] = array_replace_recursive(
126  [
127  'useSortable' => true,
128  'headerThumbnail' => [
129  'height' => '45m',
130  ],
131  'enabledControls' => [
132  'edit' => true,
133  'info' => true,
134  'dragdrop' => true,
135  'sort' => false,
136  'hide' => true,
137  'delete' => true,
138  'localize' => true,
139  ],
140  ],
141  $result['processedTca']['columns'][$fieldName]['config']['appearance'] ?? []
142  );
143 
144  return $result;
145  }
146 
151  protected function ‪resolveFileReferences(array $result, string $fieldName): array
152  {
153  if ($result['defaultLanguageRow'] !== null) {
154  return $this->‪resolveFileReferenceOverlays($result, $fieldName);
155  }
156 
157  $fileReferenceUidsOfDefaultLanguageRecord = $this->‪resolveFileReferenceUids(
158  $result['processedTca']['columns'][$fieldName]['config'],
159  $result['tableName'],
160  $result['databaseRow']['uid'],
161  $result['databaseRow'][$fieldName]
162  );
163  $result['databaseRow'][$fieldName] = implode(',', $fileReferenceUidsOfDefaultLanguageRecord);
164 
165  if ($result['inlineCompileExistingChildren']) {
166  foreach ($this->‪getSubstitutedWorkspacedUids($fileReferenceUidsOfDefaultLanguageRecord) as ‪$uid) {
167  try {
168  $compiledFileReference = $this->‪compileFileReference($result, $fieldName, ‪$uid);
169  $result['processedTca']['columns'][$fieldName]['children'][] = $compiledFileReference;
170  } catch (‪DatabaseRecordException $e) {
171  // Nothing to do here, missing file reference is just not being rendered.
172  }
173  }
174  }
175  return $result;
176  }
177 
183  protected function ‪resolveFileReferenceOverlays(array $result, string $fieldName): array
184  {
185  $fileReferenceUidsOfLocalizedOverlay = [];
186  $fieldConfig = $result['processedTca']['columns'][$fieldName]['config'];
187  if ($result['command'] === 'edit') {
188  $fileReferenceUidsOfLocalizedOverlay = $this->‪resolveFileReferenceUids(
189  $fieldConfig,
190  $result['tableName'],
191  $result['databaseRow']['uid'],
192  $result['databaseRow'][$fieldName]
193  );
194  }
195  $result['databaseRow'][$fieldName] = implode(',', $fileReferenceUidsOfLocalizedOverlay);
196  $fileReferenceUidsOfLocalizedOverlay = $this->‪getSubstitutedWorkspacedUids($fileReferenceUidsOfLocalizedOverlay);
197  if ($result['inlineCompileExistingChildren']) {
198  $tableNameWithDefaultRecords = $result['tableName'];
199  $fileReferenceUidsOfDefaultLanguageRecord = $this->‪getSubstitutedWorkspacedUids(
201  $fieldConfig,
202  $tableNameWithDefaultRecords,
203  $result['defaultLanguageRow']['uid'],
204  $result['defaultLanguageRow'][$fieldName]
205  )
206  );
207 
208  // Find which records are localized, which records are not localized and which are
209  // localized but miss default language record
210  $fieldNameWithDefaultLanguageUid = (string)(‪$GLOBALS['TCA'][self::FILE_REFERENCE_TABLE]['ctrl']['transOrigPointerField'] ?? '');
211  foreach ($fileReferenceUidsOfLocalizedOverlay as $localizedUid) {
212  try {
213  $localizedRecord = $this->‪getRecordFromDatabase(self::FILE_REFERENCE_TABLE, $localizedUid);
214  } catch (‪DatabaseRecordException $e) {
215  // The child could not be compiled, probably it was deleted and a dangling mm record exists
216  $this->logger->warning(
217  $e->getMessage(),
218  [
219  'table' => self::FILE_REFERENCE_TABLE,
220  'uid' => $localizedUid,
221  'exception' => $e,
222  ]
223  );
224  continue;
225  }
226  $uidOfDefaultLanguageRecord = (int)$localizedRecord[$fieldNameWithDefaultLanguageUid];
227  if (in_array($uidOfDefaultLanguageRecord, $fileReferenceUidsOfDefaultLanguageRecord, true)) {
228  // This localized child has a default language record. Remove this record from list of default language records
229  $fileReferenceUidsOfDefaultLanguageRecord = array_diff($fileReferenceUidsOfDefaultLanguageRecord, [$uidOfDefaultLanguageRecord]);
230  }
231  // Compile localized record
232  $compiledFileReference = $this->‪compileFileReference($result, $fieldName, $localizedUid);
233  $result['processedTca']['columns'][$fieldName]['children'][] = $compiledFileReference;
234  }
235  if ($fieldConfig['appearance']['showPossibleLocalizationRecords'] ?? false) {
236  foreach ($fileReferenceUidsOfDefaultLanguageRecord as $defaultLanguageUid) {
237  // If there are still uids in $connectedUidsOfDefaultLanguageRecord, these are records that
238  // exist in default language, but are not localized yet. Compile and mark those
239  try {
240  $compiledFileReference = $this->‪compileFileReference($result, $fieldName, $defaultLanguageUid);
241  } catch (‪DatabaseRecordException $e) {
242  // The child could not be compiled, probably it was deleted and a dangling mm record exists
243  $this->logger->warning(
244  $e->getMessage(),
245  [
246  'table' => self::FILE_REFERENCE_TABLE,
247  'uid' => $defaultLanguageUid,
248  'exception' => $e,
249  ]
250  );
251  continue;
252  }
253  $compiledFileReference['isInlineDefaultLanguageRecordInLocalizedParentContext'] = true;
254  $result['processedTca']['columns'][$fieldName]['children'][] = $compiledFileReference;
255  }
256  }
257  }
258 
259  return $result;
260  }
261 
262  protected function ‪compileFileReference(array $result, string $parentFieldName, int $childUid): array
263  {
264  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
265  $inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
266  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0) ?: [];
267 
268  return GeneralUtility::makeInstance(FormDataCompiler::class)
269  ->compile(
270  [
271  'request' => $result['request'],
272  'command' => 'edit',
273  'tableName' => self::FILE_REFERENCE_TABLE,
274  'vanillaUid' => $childUid,
275  'returnUrl' => $result['returnUrl'],
276  'isInlineChild' => true,
277  'inlineStructure' => $result['inlineStructure'],
278  'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
279  'inlineFirstPid' => $result['inlineFirstPid'],
280  'inlineParentConfig' => $result['processedTca']['columns'][$parentFieldName]['config'],
281  'inlineParentUid' => $result['databaseRow']['uid'],
282  'inlineParentTableName' => $result['tableName'],
283  'inlineParentFieldName' => $parentFieldName,
284  'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'] ?? '',
285  'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'] ?? '',
286  'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'] ?? '',
287  ],
288  GeneralUtility::makeInstance(TcaDatabaseRecord::class)
289  );
290  }
291 
298  protected function ‪getSubstitutedWorkspacedUids(array $connectedUids): array
299  {
300  $workspace = $this->‪getBackendUser()->workspace;
301  if ($workspace === 0 || !BackendUtility::isTableWorkspaceEnabled(self::FILE_REFERENCE_TABLE)) {
302  return $connectedUids;
303  }
304 
305  $substitutedUids = [];
306  foreach ($connectedUids as ‪$uid) {
307  $workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord(
308  $workspace,
309  self::FILE_REFERENCE_TABLE,
310  ‪$uid,
311  'uid,t3ver_state'
312  );
313  if (!empty($workspaceVersion)) {
314  $versionState = VersionState::tryFrom($workspaceVersion['t3ver_state'] ?? 0);
315  if ($versionState === VersionState::DELETE_PLACEHOLDER) {
316  continue;
317  }
318  ‪$uid = $workspaceVersion['uid'];
319  }
320  $substitutedUids[] = (int)‪$uid;
321  }
322  return $substitutedUids;
323  }
324 
330  protected function ‪resolveFileReferenceUids(
331  array $parentConfig,
332  $parentTableName,
333  $parentUid,
334  $parentFieldValue
335  ): array {
336  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
337  $relationHandler->start(
338  $parentFieldValue,
339  self::FILE_REFERENCE_TABLE,
340  '',
341  BackendUtility::getLiveVersionIdOfRecord($parentTableName, $parentUid) ?? $parentUid,
342  $parentTableName,
343  $parentConfig
344  );
345  return array_map(intval(...), $relationHandler->getValueArray());
346  }
347 
349  {
350  return ‪$GLOBALS['BE_USER'];
351  }
352 
354  {
355  return ‪$GLOBALS['LANG'];
356  }
357 }
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\FOREIGN_SELECTOR
‪const FOREIGN_SELECTOR
Definition: TcaFiles.php:39
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\compileFileReference
‪compileFileReference(array $result, string $parentFieldName, int $childUid)
Definition: TcaFiles.php:262
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractDatabaseRecordProvider
Definition: AbstractDatabaseRecordProvider.php:31
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:36
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\initializeAppearance
‪initializeAppearance(array $result, string $fieldName)
Definition: TcaFiles.php:123
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\initializeMinMaxItems
‪initializeMinMaxItems(array $result, string $fieldName)
Definition: TcaFiles.php:95
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\getBackendUser
‪getBackendUser()
Definition: TcaFiles.php:348
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\getSubstitutedWorkspacedUids
‪int[] getSubstitutedWorkspacedUids(array $connectedUids)
Definition: TcaFiles.php:298
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\resolveFileReferenceUids
‪int[] resolveFileReferenceUids(array $parentConfig, $parentTableName, $parentUid, $parentFieldValue)
Definition: TcaFiles.php:330
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractDatabaseRecordProvider\getRecordFromDatabase
‪array getRecordFromDatabase($tableName, $uid)
Definition: AbstractDatabaseRecordProvider.php:44
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\resolveFileReferenceOverlays
‪resolveFileReferenceOverlays(array $result, string $fieldName)
Definition: TcaFiles.php:183
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:61
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\initializeParentSysLanguageUid
‪initializeParentSysLanguageUid(array $result, string $fieldName)
Definition: TcaFiles.php:105
‪TYPO3\CMS\Backend\Form\FormDataProvider
Definition: AbstractDatabaseRecordProvider.php:16
‪TYPO3\CMS\Backend\Form\FormDataProviderInterface
Definition: FormDataProviderInterface.php:23
‪TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException
Definition: DatabaseRecordException.php:24
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\FILE_REFERENCE_TABLE
‪const FILE_REFERENCE_TABLE
Definition: TcaFiles.php:38
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\getLanguageService
‪getLanguageService()
Definition: TcaFiles.php:353
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles
Definition: TcaFiles.php:37
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:32
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Form\FormDataCompiler
Definition: FormDataCompiler.php:26
‪TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord
Definition: TcaDatabaseRecord.php:25
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\resolveFileReferences
‪resolveFileReferences(array $result, string $fieldName)
Definition: TcaFiles.php:151
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles\addData
‪addData(array $result)
Definition: TcaFiles.php:41