diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-84184-ShowColumnsSelectionInFilelist.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-84184-ShowColumnsSelectionInFilelist.rst new file mode 100644 index 0000000000000000000000000000000000000000..451c09fa4c82e152a45148878adff0c0826d1d6d --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-84184-ShowColumnsSelectionInFilelist.rst @@ -0,0 +1,38 @@ +.. include:: ../../Includes.txt + +==================================================== +Feature: #84184 - Show columns selection in filelist +==================================================== + +See :issue:`84184` + +Description +=========== + +The column selector, introduced in :issue:`94218` and improved +in :issue:`94474` is now also available in the filelist module. + +As already known from the recordlist, it can be used to manage the fields, +displayed for each file / folder, while containing convenience actions, +such as "filter", "check all / none" and "toggle selection". + +The fields to be selected are a combination of special fields, such as +`references` or `read/write` permissions, the corresponding `sys_file` +record fields, as well as all available `sys_file_metadata` fields. + +Administrators can manage whether the column selection is available +for their users with a new user TSconfig option: + +.. code-block:: typoscript + + # disable the column selector + file_list.displayColumnSelector = 0 + + +Impact +====== + +It's now possible to manage the displayed fields for files / folders +in the filelist module, using the columns selection component. + +.. index:: Backend diff --git a/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf b/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf index 86d0b98aa7b31443d060f592ed0c263f907a5602..686008f851ff00727bb61f00de5497be84f4a926 100644 --- a/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf +++ b/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf @@ -134,13 +134,13 @@ Do you want to continue WITHOUT saving?</source> <source>Parent page ID</source> </trans-unit> <trans-unit id="labels.tstamp" resname="labels.tstamp"> - <source>Last changed</source> + <source>Last modified</source> </trans-unit> <trans-unit id="labels.crdate" resname="labels.crdate"> <source>Creation date</source> </trans-unit> <trans-unit id="labels.cruser_id" resname="labels.cruser_id"> - <source>Creator</source> + <source>Created by</source> </trans-unit> <trans-unit id="labels.sorting" resname="labels.sorting"> <source>Sorting</source> diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php index a9c24cacbe54f2d111ff08f4b2af3673e1ad2adc..93520758bf26cb7986e28e9b921a2e4e8f7415dd 100644 --- a/typo3/sysext/filelist/Classes/Controller/FileListController.php +++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php @@ -270,6 +270,7 @@ class FileListController implements LoggerAwareInterface $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileDelete'); $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu'); $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/MultiRecordSelection'); + $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/ColumnSelectorButton'); $this->pageRenderer->addInlineLanguageLabelFile( 'EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf', 'buttons' @@ -370,6 +371,7 @@ class FileListController implements LoggerAwareInterface (string)($this->MOD_SETTINGS['sort'] ?? ''), (bool)($this->MOD_SETTINGS['reverse'] ?? false) ); + $this->filelist->setColumnsToRender($this->getBackendUser()->getModuleData('list/displayFields')['_FILE'] ?? []); } protected function generateFileList(): void @@ -385,6 +387,7 @@ class FileListController implements LoggerAwareInterface if ($this->folderObject->getStorage()->isBrowsable()) { $this->view->assignMultiple([ 'listHtml' => $this->filelist->getTable($searchDemand), + 'listUrl' => $this->filelist->listURL(), 'totalItems' => $this->filelist->totalItems ]); if ($this->filelist->totalItems === 0 && $searchDemand !== null) { @@ -404,6 +407,20 @@ class FileListController implements LoggerAwareInterface 'returnUrl' => $this->filelist->listURL() ]) ]); + + // Add column selector information if enabled + if ($this->getBackendUser()->getTSConfig()['options.']['file_list.']['displayColumnSelector'] ?? true) { + $this->view->assign('columnSelector', [ + 'url' => $this->uriBuilder->buildUriFromRoute( + 'ajax_record_show_columns_selector', + ['id' => $this->id, 'table' => '_FILE'] + ), + 'title' => sprintf( + $lang->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang.xlf:showColumnsSelection'), + $lang->sL($GLOBALS['TCA']['sys_file']['ctrl']['title'] ?? ''), + ), + ]); + } } else { $this->addFlashMessage( $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:storageNotBrowsableMessage'), diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php index 55df91ec3ebcc9fc3347cf6d3cd2d15202722008..459fcf7aff35ca69541a7e7f5feea1e08f162ff4 100644 --- a/typo3/sysext/filelist/Classes/FileList.php +++ b/typo3/sysext/filelist/Classes/FileList.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Filelist; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Backend\Avatar\Avatar; use TYPO3\CMS\Backend\Clipboard\Clipboard; use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -37,6 +38,7 @@ use TYPO3\CMS\Core\Resource\ProcessedFile; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceInterface; use TYPO3\CMS\Core\Resource\Search\FileSearchDemand; +use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Resource\Utility\ListUtility; use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -137,7 +139,7 @@ class FileList '_CONTROL_' => 'col-control', '_SELECTOR_' => 'col-selector', 'icon' => 'col-icon', - 'file' => 'col-title col-responsive', + 'name' => 'col-title col-responsive', '_LOCALIZATION_' => 'col-localizationa', ]; @@ -180,6 +182,12 @@ class FileList protected array $selectedElements = []; + /** + * A runtime first-level cache to avoid unneeded calls to BackendUtility::getRecord() + * @var array + */ + protected array $backendUserCache = []; + public function __construct(?ServerRequestInterface $request = null) { // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL) @@ -217,10 +225,15 @@ class FileList $this->sortRev = $sortRev; $this->firstElementNumber = $pointer; $this->fieldArray = [ - '_SELECTOR_', 'icon', 'file', '_LOCALIZATION_', '_CONTROL_', 'fileext', 'tstamp', 'size', 'rw', '_REF_' + '_SELECTOR_', 'icon', 'name', '_LOCALIZATION_', '_CONTROL_', 'record_type', 'size', 'rw', '_REF_' ]; } + public function setColumnsToRender(array $additionalFields = []): void + { + $this->fieldArray = array_unique(array_merge($this->fieldArray, $additionalFields)); + } + /** * Returns a table with directories and files listed. * @@ -327,16 +340,16 @@ class FileList // Header line is drawn $theData = []; - foreach ($this->fieldArray as $v) { - if ($v === '_SELECTOR_') { - $theData[$v] = $this->renderCheckboxActions(); - } elseif ($v === '_REF_') { - $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._REF_')); - } elseif ($v === '_PATH_') { - $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._PATH_')); - } elseif ($v !== 'icon') { - // Normal row - except "icon", which does not need a table header col - $theData[$v] = $this->linkWrapSort($v); + foreach ($this->fieldArray as $fieldName) { + if ($fieldName === '_SELECTOR_') { + $theData[$fieldName] = $this->renderCheckboxActions(); + } elseif ($specialLabel = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $fieldName)) { + $theData[$fieldName] = htmlspecialchars($specialLabel); + } elseif ($customLabel = $this->getLanguageService()->getLL('c_' . $fieldName)) { + $theData[$fieldName] = $this->linkWrapSort($fieldName, $customLabel); + } elseif ($fieldName !== 'icon') { + // Normal database field + $theData[$fieldName] = $this->linkWrapSort($fieldName); } } @@ -409,7 +422,7 @@ class FileList // Reverse $theData = []; $href = $this->listURL(['pointer' => ($currentItemCount - $this->iLimit)]); - $theData['file'] = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon( + $theData['name'] = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon( 'actions-move-up', Icon::SIZE_SMALL )->render() . ' <i>[' . (max(0, $currentItemCount - $this->iLimit) + 1) . ' - ' . $currentItemCount . ']</i></a>'; @@ -421,7 +434,7 @@ class FileList // Forward $theData = []; $href = $this->listURL(['pointer' => $currentItemCount]); - $theData['file'] = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon( + $theData['name'] = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon( 'actions-move-down', Icon::SIZE_SMALL )->render() . ' <i>[' . ($currentItemCount + 1) . ' - ' . $this->totalItems . ']</i></a>'; @@ -494,7 +507,7 @@ class FileList $theData[$field] = ''; } $theData['icon'] = $theIcon; - $theData['file'] = $displayName; + $theData['name'] = $displayName; } else { foreach ($this->fieldArray as $field) { switch ($field) { @@ -509,17 +522,13 @@ class FileList case 'rw': $theData[$field] = '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>'); break; - case 'fileext': + case 'record_type': $theData[$field] = htmlspecialchars($this->getLanguageService()->getLL('folder')); break; - case 'tstamp': - $tstamp = $folderObject->getModificationTime(); - $theData[$field] = $tstamp ? BackendUtility::date($tstamp) : '-'; - break; case 'icon': $theData[$field] = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier()); break; - case 'file': + case 'name': $theData[$field] = $this->linkWrapDir($displayName, $folderObject); break; case '_CONTROL_': @@ -666,11 +675,8 @@ class FileList case 'rw': $theData[$field] = '' . (!$fileObject->checkActionPermission('read') ? ' ' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>') . (!$fileObject->checkActionPermission('write') ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>'); break; - case 'fileext': - $theData[$field] = htmlspecialchars(strtoupper($ext)); - break; - case 'tstamp': - $theData[$field] = BackendUtility::date($fileObject->getModificationTime()); + case 'record_type': + $theData[$field] = htmlspecialchars($this->getLanguageService()->getLL('file') . ($ext ? ' (' . strtoupper($ext) . ')' : '')); break; case '_CONTROL_': $theData[$field] = $this->makeEdit($fileObject); @@ -733,7 +739,7 @@ class FileList case 'icon': $theData[$field] = (string)BackendUtility::wrapClickMenuOnIcon($this->getFileOrFolderIcon($fileName, $fileObject), 'sys_file', $fileObject->getCombinedIdentifier()); break; - case 'file': + case 'name': // Edit metadata of file $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject); @@ -759,7 +765,33 @@ class FileList default: $theData[$field] = ''; if ($fileObject->hasProperty($field)) { - $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL)); + $concreteTableName = $this->getConcreteTableName($field); + if ($field === ($GLOBALS['TCA'][$concreteTableName]['ctrl']['cruser_id'] ?? '')) { + // Handle cruser_id by adding the avatar along with the username + $theData[$field] = $this->getBackendUserInformation((int)$fileObject->getProperty($field)); + } elseif ($field === ($GLOBALS['TCA'][$concreteTableName]['ctrl']['crdate'] ?? '')) { + // This special case is needed since crdate is defined as "passthrough" + // in sys_file_metadata due to the file search API and will therefore not + // be processed by getProcessedValue. + $theData[$field] = htmlspecialchars(BackendUtility::datetime((int)$fileObject->getProperty($field))); + } elseif ($field === 'storage') { + // Fetch storage name of the current file + $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid((int)$fileObject->getProperty($field)); + if ($storage !== null) { + $theData[$field] = htmlspecialchars($storage->getName()); + } + } else { + $theData[$field] = htmlspecialchars( + BackendUtility::getProcessedValueExtra( + $this->getConcreteTableName($field), + $field, + $fileObject->getProperty($field), + $this->fixedL, + $fileObject->getMetaData()->offsetGet('uid'), + true + ) + ); + } } } } @@ -801,17 +833,33 @@ class FileList } /** - * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands... + * Wraps a field label for the header row into a link to the filelist with sorting commands * - * @param string $col Sorting column - * @return string HTML + * @param string $fieldName The field to sort + * @param string $label The label to be wrapped - will be determined if not given + * @return string The constructed link - HTML */ - public function linkWrapSort($col) + public function linkWrapSort(string $fieldName, string $label = ''): string { - $code = htmlspecialchars($this->getLanguageService()->getLL('c_' . $col)); - $params = ['SET' => ['sort' => $col], 'pointer' => 0]; + // Determine label if not given + if ($label === '') { + $lang = $this->getLanguageService(); + $concreteTableName = $this->getConcreteTableName($fieldName); + $label = $lang->sL(BackendUtility::getItemLabel($concreteTableName, $fieldName) ?? ''); + if ($label !== '') { + // In case global TSconfig exists we have to check if the label is overridden there + $tsConfig = BackendUtility::getPagesTSconfig(0); + if (!empty($tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label.'][$lang->lang])) { + $label = $tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label.'][$lang->lang]; + } elseif (!empty($tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label'])) { + $label = $tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label']; + } + } + } + + $params = ['SET' => ['sort' => $fieldName], 'pointer' => 0]; - if ($this->sort === $col) { + if ($this->sort === $fieldName) { // Check reverse sorting $params['SET']['reverse'] = ($this->sortRev ? '0' : '1'); $sortArrow = $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render(); @@ -820,7 +868,8 @@ class FileList $sortArrow = ''; } $href = $this->listURL($params); - return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>'; + + return '<a href="' . htmlspecialchars($href) . '">' . htmlspecialchars($label) . ' ' . $sortArrow . '</a>'; } /** @@ -1273,6 +1322,38 @@ class FileList </div>'; } + /** + * Helper method around fetching a "cruser_id" information for a record, with a cache, so the same information + * does not have to be processed for the same user over and over again. + */ + protected function getBackendUserInformation(int $backendUserId): string + { + if (!isset($this->backendUserCache[$backendUserId])) { + $beUserRecord = BackendUtility::getRecord('be_users', $backendUserId); + if (is_array($beUserRecord)) { + $avatar = GeneralUtility::makeInstance(Avatar::class); + $label = htmlspecialchars(BackendUtility::getRecordTitle('be_users', $beUserRecord)); + $content = $avatar->render($beUserRecord) . '<strong>' . $label . '</strong>'; + } else { + $content = '<strong>–</strong>'; + } + $this->backendUserCache[$backendUserId] = $content; + } + return $this->backendUserCache[$backendUserId]; + } + + /** + * Determine the concrete table name by checking if + * the field exists, while sys_file takes precedence. + * + * @param string $fieldName + * @return string + */ + protected function getConcreteTableName(string $fieldName): string + { + return ($GLOBALS['TCA']['sys_file']['columns'][$fieldName] ?? false) ? 'sys_file' : 'sys_file_metadata'; + } + /** * Returns an instance of LanguageService * diff --git a/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf b/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf index 9cfbc2b88566e28450337bf0c75d585f2993a1a7..75973249fb62b212137c28c6418c22d37e4ee2c9 100644 --- a/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf +++ b/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf @@ -27,21 +27,15 @@ <trans-unit id="displayThumbs" resname="displayThumbs"> <source>Display thumbnails</source> </trans-unit> - <trans-unit id="c_file" resname="c_file"> - <source>File Name</source> - </trans-unit> - <trans-unit id="c_filepath" resname="c_filepath"> - <source>File Path</source> + <trans-unit id="c_name" resname="c_name"> + <source>Name</source> </trans-unit> <trans-unit id="c_size" resname="c_size"> <source>Size</source> </trans-unit> - <trans-unit id="c_fileext" resname="c_fileext"> + <trans-unit id="c_record_type" resname="c_record_type"> <source>Type</source> </trans-unit> - <trans-unit id="c_tstamp" resname="c_tstamp"> - <source>Last Modified</source> - </trans-unit> <trans-unit id="c_rw" resname="c_rw"> <source>RW</source> </trans-unit> diff --git a/typo3/sysext/filelist/Resources/Private/Templates/File/List.html b/typo3/sysext/filelist/Resources/Private/Templates/File/List.html index 42246ec6dc709ac0384464be64e321bcaf5b40b2..74653e46b7552e76df652690f33ed2891a508065 100644 --- a/typo3/sysext/filelist/Resources/Private/Templates/File/List.html +++ b/typo3/sysext/filelist/Resources/Private/Templates/File/List.html @@ -76,14 +76,38 @@ </div> </f:if> </div> - <f:if condition="{listHtml} && {displayThumbs.enabled}"> + <f:if condition="{listHtml}"> <div class="col-auto"> - <div class="form-check form-switch"> - {displayThumbs.html -> f:format.raw()} - <label for="checkDisplayThumbs" class="form-check-label"> - {displayThumbs.label} - </label> - </div> + <f:if condition="{columnSelector} || {displayThumbs.enabled}"> + <div class="row row-cols-auto align-items-center gx-3"> + <f:if condition="{columnSelector}"> + <div class="col"> + <typo3-recordlist-column-selector-button + url="{columnSelector.url}" + target="{listUrl}" + title="{columnSelector.title}" + ok="{f:translate(key: 'LLL:EXT:recordlist/Resources/Private/Language/locallang.xlf:updateColumnView')}" + close="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel')}" + error="{f:translate(key: 'LLL:EXT:recordlist/Resources/Private/Language/locallang.xlf:updateColumnView.error')}"> + <button type="button" class="btn btn-default btn-sm" title="{columnSelector.title}"> + <core:icon identifier="actions-options" size="small" /> + <f:translate key="LLL:EXT:recordlist/Resources/Private/Language/locallang.xlf:showColumns" /> + </button> + </typo3-recordlist-column-selector-button> + </div> + </f:if> + <f:if condition="{displayThumbs.enabled}"> + <div class="col"> + <div class="form-check form-switch mb-0"> + {displayThumbs.html -> f:format.raw()} + <label for="checkDisplayThumbs" class="form-check-label"> + {displayThumbs.label} + </label> + </div> + </div> + </f:if> + </div> + </f:if> </div> </f:if> </div> diff --git a/typo3/sysext/recordlist/Classes/Controller/ColumnSelectorController.php b/typo3/sysext/recordlist/Classes/Controller/ColumnSelectorController.php index 6ba7ccd5703d7e8af1231ffdcab5ab64a069bb32..33dbbb678bd8ad36d277fe96cb0ff92f562f05d3 100644 --- a/typo3/sysext/recordlist/Classes/Controller/ColumnSelectorController.php +++ b/typo3/sysext/recordlist/Classes/Controller/ColumnSelectorController.php @@ -36,6 +36,18 @@ use TYPO3Fluid\Fluid\View\ViewInterface; class ColumnSelectorController { private const PSEUDO_FIELDS = ['_REF_', '_PATH_']; + private const EXCLUDE_FILE_FIELDS = [ + 'pid', // Not relevant as all records are on pid=0 + 'identifier', // Handled manually in listing + 'name', // Handled manually in listing + 'metadata', // The reference to the meta data is not relevant + 'file', // The reference to the file is not relevant + 'sys_language_uid', // Not relevant in listing since only defualt is displayed + 'l10n_parent', // Not relevant in listing + 't3ver_state', // Not relevant in listing + 't3ver_wsid', // Not relevant in listing + 't3ver_oid' // Not relevant in listing + ]; protected ResponseFactoryInterface $responseFactory; @@ -56,7 +68,7 @@ class ColumnSelectorController $table = (string)($parsedBody['table'] ?? ''); $selectedColumns = $parsedBody['selectedColumns'] ?? []; - if ($table === '' || !is_array($selectedColumns) || $selectedColumns === []) { + if ($table === '' || !is_array($selectedColumns)) { return $this->jsonResponse([ 'success' => false, 'message' => htmlspecialchars( @@ -115,29 +127,42 @@ class ColumnSelectorController // Current fields selection $displayFields = $this->getBackendUserAuthentication()->getModuleData('list/displayFields')[$table] ?? []; - // Request fields from table and add pseudo fields - $fields = array_merge( - GeneralUtility::makeInstance(DatabaseRecordList::class)->makeFieldList($table, false, true), - self::PSEUDO_FIELDS - ); + if ($table === '_FILE') { + // Special handling for _FILE (merging sys_file and sys_file_metadata together) + $fields = $this->getFileFields(); + } else { + // Request fields from table and add pseudo fields + $fields = array_merge( + GeneralUtility::makeInstance(DatabaseRecordList::class)->makeFieldList($table, false, true), + self::PSEUDO_FIELDS + ); + } $columns = $specialColumns = $disabledColumns = []; foreach ($fields as $fieldName) { + $concreteTableName = $table; + + // In case we deal with _FILE, the field name is prefixed with the + // concrete table name, which is either sys_file or sys_file_metadata. + if ($table === '_FILE') { + [$concreteTableName, $fieldName] = explode('|', $fieldName); + } + // Hide field if disabled - if ($tsConfig['TCEFORM.'][$table . '.'][$fieldName . '.']['disabled'] ?? false) { + if ($tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['disabled'] ?? false) { continue; } // Determine if the column should be disabled (Meaning it is always selected and can not be turned off) - $isDisabled = $fieldName === ($GLOBALS['TCA'][$table]['ctrl']['label'] ?? false); + $isDisabled = $fieldName === ($GLOBALS['TCA'][$concreteTableName]['ctrl']['label'] ?? false); // Determine field label - $label = BackendUtility::getItemLabel($table, $fieldName); + $label = BackendUtility::getItemLabel($concreteTableName, $fieldName); if ($label) { - if (!empty($tsConfig['TCEFORM.'][$table . '.'][$fieldName . '.']['label.'][$this->getLanguageService()->lang])) { - $label = $tsConfig['TCEFORM.'][$table . '.'][$fieldName . '.']['label.'][$this->getLanguageService()->lang]; - } elseif (!empty($tsConfig['TCEFORM.'][$table . '.'][$fieldName . '.']['label'])) { - $label = $tsConfig['TCEFORM.'][$table . '.'][$fieldName . '.']['label']; + if (!empty($tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label.'][$this->getLanguageService()->lang])) { + $label = $tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label.'][$this->getLanguageService()->lang]; + } elseif (!empty($tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label'])) { + $label = $tsConfig['TCEFORM.'][$concreteTableName . '.'][$fieldName . '.']['label']; } } elseif ($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $fieldName)) { $label = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $fieldName; @@ -172,6 +197,36 @@ class ColumnSelectorController return array_merge($disabledColumns, $columns, $specialColumns); } + /** + * Get file related fields by merging sys_file and sys_file_metadata together + * and adding the corresponding table as prefix (needed for labels processing). + * + * @return array + */ + protected function getFileFields(): array + { + // Get all sys_file fields expect excluded ones + $fileFields = array_filter( + GeneralUtility::makeInstance(DatabaseRecordList::class)->makeFieldList('sys_file', false, true), + static fn (string $field): bool => !in_array($field, self::EXCLUDE_FILE_FIELDS, true) + ); + + // Update the exclude fields with the fields, already added through sys_file, since those take precedence + $excludeFields = array_merge($fileFields, self::EXCLUDE_FILE_FIELDS); + + // Get all sys_file_metadata fields expect excluded ones + $fileMetaDataFields = array_filter( + GeneralUtility::makeInstance(DatabaseRecordList::class)->makeFieldList('sys_file_metadata', false, true), + static fn (string $field): bool => !in_array($field, $excludeFields, true) + ); + + // Merge sys_file and sys_file_metadata fields together, while adding the table name as prefix + return array_merge( + array_map(static fn (string $value): string => 'sys_file|' . $value, $fileFields), + array_map(static fn (string $value): string => 'sys_file_metadata|' . $value, $fileMetaDataFields), + ); + } + protected function htmlResponse(ViewInterface $view): ResponseInterface { $response = $this->responseFactory