][][] => data------- $formPrefix = $this->inlineStackProcessor->getFormPrefixFromStructure($this->data['inlineStructure']); $domObjectId = $this->inlineStackProcessor->getDomObjectIdPrefixFromStructure($this->data['inlineStructure'], $this->data['inlineFirstPid']); $this->fileReferenceData = $this->data['inlineData']; $this->fileReferenceData['map'][$formPrefix] = $domObjectId; $resultArray = $this->initializeResultArray(); $resultArray['inlineData'] = $this->fileReferenceData; $html = ''; $classes = []; $combinationHtml = ''; $record = $this->data['databaseRow']; $uid = $record['uid'] ?? 0; $appendFormFieldNames = '[' . self::FILE_REFERENCE_TABLE . '][' . $uid . ']'; $objectId = $domObjectId . '-' . self::FILE_REFERENCE_TABLE . '-' . $uid; $isNewRecord = $this->data['command'] === 'new'; $hiddenFieldName = (string)($this->data['processedTca']['ctrl']['enablecolumns']['disabled'] ?? ''); if (!$this->data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { if ($isNewRecord || $this->data['isInlineChildExpanded']) { $fileReferenceData = $this->renderFileReference($this->data); $html = $fileReferenceData['html']; $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fileReferenceData, false); } else { // This class is the marker for the JS-function to check if the full content has already been loaded $classes[] = 't3js-not-loaded'; } if ($isNewRecord) { // Add pid of file reference as hidden field $html .= ''; // Tell DataHandler this file reference is expanded $ucFieldName = 'uc[inlineView]' . '[' . $this->data['inlineTopMostParentTableName'] . ']' . '[' . $this->data['inlineTopMostParentUid'] . ']' . htmlspecialchars($appendFormFieldNames); $html .= ''; } else { // Set additional field for processing for saving $html .= ''; if ($hiddenFieldName !== '' && (!$this->data['isInlineChildExpanded'] || !in_array($hiddenFieldName, $this->data['columnsToProcess'], true)) ) { $isHidden = (bool)($record[$hiddenFieldName] ?? false); $html .= ''; $html .= ''; } } } if ($this->data['inlineParentConfig']['renderFieldsOnly'] ?? false) { // Render "body" part only $resultArray['html'] = $html . $combinationHtml; return $resultArray; } // Render header row and content (if expanded) if ($this->data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { $classes[] = 'panel-placeholder'; } if ($record[$hiddenFieldName] ?? false) { $classes[] = 'panel-hidden'; } if ($isNewRecord) { $classes[] = 'inlineIsNewRecord'; } // The hashed object id needs a non-numeric prefix, the value is used as ID selector in JavaScript $hashedObjectId = 'hash-' . md5($objectId); $containerAttributes = [ 'id' => $objectId . '_div', 'class' => 'form-irre-object panel panel-default ' . trim(implode(' ', $classes)), 'data-object-uid' => $record['uid'] ?? 0, 'data-object-id' => $objectId, 'data-object-id-hash' => $hashedObjectId, 'data-object-parent-group' => $domObjectId . '-' . self::FILE_REFERENCE_TABLE, 'data-field-name' => $appendFormFieldNames, 'data-topmost-parent-table' => $this->data['inlineTopMostParentTableName'], 'data-topmost-parent-uid' => $this->data['inlineTopMostParentUid'], 'data-placeholder-record' => $this->data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ? '1' : '0', ]; $isExpanded = $this->data['isInlineChildExpanded'] ?? false; $ariaControls = htmlspecialchars($objectId . '_fields', ENT_QUOTES | ENT_HTML5); $resultArray['html'] = '
' . $this->renderFileHeader($isExpanded, $ariaControls) . '
' . $html . $combinationHtml . '
'; return $resultArray; } protected function renderFileReference(array $data): array { $data['tabAndInlineStack'][] = [ 'inline', $this->inlineStackProcessor->getDomObjectIdPrefixFromStructure($this->data['inlineStructure'], $data['inlineFirstPid']) . '-' . $data['tableName'] . '-' . $data['databaseRow']['uid'], ]; return $this->nodeFactory->create(array_replace_recursive($data, [ 'inlineData' => $this->fileReferenceData, 'renderType' => 'fullRecordContainer', ]))->render(); } /** * Renders the HTML header for the file, such as the title, toggle-function, drag'n'drop, etc. * Later on the command-icons are inserted here, too. */ protected function renderFileHeader(bool $isExpanded, string $ariaControls): string { $languageService = $this->getLanguageService(); $databaseRow = $this->data['databaseRow']; $recordTitle = $this->getRecordTitle(); if (empty($recordTitle)) { $recordTitle = '[' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']'; } $objectId = htmlspecialchars($this->inlineStackProcessor->getDomObjectIdPrefixFromStructure($this->data['inlineStructure'], $this->data['inlineFirstPid']) . '-' . self::FILE_REFERENCE_TABLE . '-' . ($databaseRow['uid'] ?? 0)); $altText = BackendUtility::getRecordIconAltText($databaseRow, self::FILE_REFERENCE_TABLE, false); // Renders the header image (thumbnail, icon, or missing file indicator) $headerImage = ''; $headerBadge = ''; $isMissing = false; if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) { $fileUid = $databaseRow[self::FOREIGN_SELECTOR][0]['uid'] ?? null; if (!empty($fileUid)) { try { $fileObject = $this->resourceFactory->getFileObject($fileUid); if ($fileObject->isMissing()) { $isMissing = true; $recordTitle = htmlspecialchars($fileObject->getName()); $headerImage = '
' . $this->iconFactory->getIcon('default-not-found', IconSize::SMALL)->render() . '
'; $headerBadge = '
' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing')) . '
'; } elseif ($fileObject->isImage() || $fileObject->isMediaFile()) { $imageSetup = $this->data['inlineParentConfig']['appearance']['headerThumbnail'] ?? []; $cropVariantCollection = CropVariantCollection::create($databaseRow['crop'] ?? ''); if (!$cropVariantCollection->getCropArea()->isEmpty()) { $imageSetup['crop'] = $cropVariantCollection->getCropArea()->makeAbsoluteBasedOnFile($fileObject); } $processedImage = $fileObject->process( ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, array_merge(['maxWidth' => 60, 'maxHeight' => 45], $imageSetup) ); // Only use a thumbnail if the processing process was successful by checking if image width is set if ($processedImage->getProperty('width')) { $imageUrl = $processedImage->getPublicUrl() ?? ''; $headerImage = '
'; } } } catch (\InvalidArgumentException $e) { $fileObject = null; } } } if ($headerImage === '' && !$isMissing) { $headerImage = '
' . $this->iconFactory ->getIconForRecord(self::FILE_REFERENCE_TABLE, $databaseRow, IconSize::SMALL) ->setTitle($altText) ->render() . '
'; } return '
' . $this->renderFileReferenceHeaderControl() . '
'; } /** * Render the control-icons for a file reference (e.g. create new, sorting, delete, disable/enable). */ protected function renderFileReferenceHeaderControl(): string { $controls = []; $databaseRow = $this->data['databaseRow']; $databaseRow += [ 'uid' => 0, ]; $parentConfig = $this->data['inlineParentConfig']; $languageService = $this->getLanguageService(); $backendUser = $this->getBackendUserAuthentication(); $isNewItem = str_starts_with((string)$databaseRow['uid'], 'NEW'); $fileReferenceTableTca = $this->data['tcaSchemata']->get(self::FILE_REFERENCE_TABLE); $calcPerms = new Permission( $backendUser->calcPerms(BackendUtility::readPageAccess( (int)($this->data['parentPageRow']['uid'] ?? 0), $backendUser->getPagePermsClause(Permission::PAGE_SHOW) )) ); $event = $this->eventDispatcher->dispatch( new ModifyFileReferenceEnabledControlsEvent($this->data, $databaseRow) ); if ($this->data['isInlineDefaultLanguageRecordInLocalizedParentContext']) { $controls['localize'] = $this->iconFactory ->getIcon('actions-edit-localize-status-low', IconSize::SMALL) ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize.isLocalizable')) ->render(); } if ($event->isControlEnabled('info')) { if ($isNewItem) { $controls['info'] = ' ' . $this->iconFactory->getIcon('empty-empty', IconSize::SMALL)->render() . ' '; } else { $controls['info'] = ' '; } } // If the table is NOT a read-only table, then show these links: if (!($parentConfig['readOnly'] ?? false) && !($fileReferenceTableTca->getCapability(TcaSchemaCapability::AccessReadOnly)->getValue() ?? false) && !($this->data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false) ) { if ($event->isControlEnabled('sort')) { $icon = 'actions-move-up'; $class = ''; if ((int)$parentConfig['inline']['first'] === (int)$databaseRow['uid']) { $class = ' disabled'; $icon = 'empty-empty'; } $controls['sort.up'] = ' '; $icon = 'actions-move-down'; $class = ''; if ((int)$parentConfig['inline']['last'] === (int)$databaseRow['uid']) { $class = ' disabled'; $icon = 'empty-empty'; } $controls['sort.down'] = ' '; } $sysFileMetadataTableTca = $this->data['tcaSchemata']->has('sys_file_metadata') ? $this->data['tcaSchemata']->get('sys_file_metadata') : null; if (!$isNewItem && ($languageField = ($sysFileMetadataTableTca?->getRawConfiguration()['languageField'] ?? false)) && $backendUser->check('tables_modify', 'sys_file_metadata') && $event->isControlEnabled('edit') ) { $languageId = (int)(is_array($databaseRow[$languageField] ?? null) ? ($databaseRow[$languageField][0] ?? 0) : ($databaseRow[$languageField] ?? 0)); $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file_metadata'); $metadataRecord = $queryBuilder ->select('uid') ->from('sys_file_metadata') ->where( $queryBuilder->expr()->eq( 'file', $queryBuilder->createNamedParameter((int)$databaseRow['uid_local'][0]['uid'], Connection::PARAM_INT) ), $queryBuilder->expr()->eq( $languageField, $queryBuilder->createNamedParameter($languageId, Connection::PARAM_INT) ) ) ->setMaxResults(1) ->executeQuery() ->fetchAssociative(); if (!empty($metadataRecord)) { $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [ 'edit[sys_file_metadata][' . (int)$metadataRecord['uid'] . ']' => 'edit', 'module' => (string)($this->data['request']->getQueryParams()['module'] ?? ''), 'returnUrl' => $this->data['returnUrl'], ]); $controls['edit'] = ' ' . $this->iconFactory->getIcon('actions-open', IconSize::SMALL)->render() . ' '; } } if ($event->isControlEnabled('delete') && $calcPerms->editContentPermissionIsGranted()) { $recordInfo = $this->data['databaseRow']['uid_local'][0]['title'] ?? $this->data['recordTitle'] ?? ''; if ($this->getBackendUserAuthentication()->shallDisplayDebugInformation()) { $recordInfo .= ' [' . $this->data['tableName'] . ':' . $this->data['vanillaUid'] . ']'; } $controls['delete'] = ' '; } if (($hiddenField = ($fileReferenceTableTca->getCapability(TcaSchemaCapability::RestrictionDisabledField)->getFieldName())) !== '' && ($fileReferenceTableTca->hasField($hiddenField)) && $event->isControlEnabled('hide') && ( !($fileReferenceTableTca->getField($hiddenField)->getConfiguration()['exclude'] ?? false) || $backendUser->check('non_exclude_fields', self::FILE_REFERENCE_TABLE . ':' . $hiddenField) ) ) { if ($databaseRow[$hiddenField] ?? false) { $controls['hide'] = ' '; } else { $controls['hide'] = ' '; } } if (($parentConfig['appearance']['useSortable'] ?? false) && $event->isControlEnabled('dragdrop')) { $controls['dragdrop'] = ' ' . $this->iconFactory->getIcon('actions-move-move', IconSize::SMALL)->render() . ' '; } } elseif (($this->data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false) && MathUtility::canBeInterpretedAsInteger($this->data['inlineParentUid']) && $event->isControlEnabled('localize') ) { $controls['localize'] = ' '; } if ($lockInfo = BackendUtility::isRecordLocked(self::FILE_REFERENCE_TABLE, $databaseRow['uid'])) { $controls['locked'] = ' '; } // Get modified controls. This means their markup was modified, new controls were added or controls got removed. $controls = $this->eventDispatcher->dispatch( new ModifyFileReferenceControlsEvent($controls, $this->data, $databaseRow) )->getControls(); $out = ''; if (($controls['edit'] ?? false) || ($controls['hide'] ?? false) || ($controls['delete'] ?? false)) { $out .= '
' . ($controls['edit'] ?? '') . ($controls['hide'] ?? '') . ($controls['delete'] ?? '') . '
'; unset($controls['edit'], $controls['hide'], $controls['delete']); } if (($controls['info'] ?? false) || ($controls['new'] ?? false) || ($controls['sort.up'] ?? false) || ($controls['sort.down'] ?? false) || ($controls['dragdrop'] ?? false)) { $out .= '
' . ($controls['info'] ?? '') . ($controls['new'] ?? '') . ($controls['sort.up'] ?? '') . ($controls['sort.down'] ?? '') . ($controls['dragdrop'] ?? '') . '
'; unset($controls['info'], $controls['new'], $controls['sort.up'], $controls['sort.down'], $controls['dragdrop']); } if ($controls['localize'] ?? false) { $out .= '
' . $controls['localize'] . '
'; unset($controls['localize']); } if ($controls !== [] && ($remainingControls = trim(implode('', $controls))) !== '') { $out .= '
' . $remainingControls . '
'; } return $out; } protected function getRecordTitle(): string { $databaseRow = $this->data['databaseRow']; $fileRecord = $databaseRow['uid_local'][0]['row'] ?? null; if ($fileRecord === null) { return $this->data['recordTitle'] ?: (string)$databaseRow['uid']; } $title = '' . $this->getLabelFieldForRecord($databaseRow, $fileRecord, 'name') . ''; // In debug mode, add the table name to the record title if ($this->getBackendUserAuthentication()->shallDisplayDebugInformation()) { $title .= ' [' . self::FILE_REFERENCE_TABLE . ']'; } return $title; } protected function getLabelFieldForRecord(array $databaseRow, array $fileRecord, string $field): string { $value = ''; if (isset($databaseRow[$field])) { $value = htmlspecialchars((string)$databaseRow[$field]); } elseif (isset($fileRecord[$field])) { $value = htmlspecialchars((string)$fileRecord[$field]); } return $value; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }