Commit 2f0c2656 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Benni Mack
Browse files

[FEATURE] Allow overriding fileFolder config with TSconfig

The "fileFolder" configuration options, available for TCA
columns of type "select" are used to fill the select field
with predefined files (images / icons). Nowadays this is
frequently used to make a corporate icon set available for
editors. In multi site installations however, those icon sets
usually differ from site to site.

Therefore, the AbstractItemProvider is now extended to allow
overriding those settings with TSconfig (TCEFORM).

Furthermore, to streamline the TCA configuration and to be
in line with the corresponding overrides, the "fileFolder"
TCA configuration options are moved into a dedicated sub array
"fileFolderConfiguration" and the properties are renamed
to be consistent with other TCA options.

* fileFolder => folder
* fileFolder_extList => allowedExtensions
* fileFolder_recursions => depth

A TCA migration is in place.

Resolves: #94406
Releases: master
Change-Id: I621198523edfd328ad68d692d9194017c445406f
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69832

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 6d956e81
......@@ -245,38 +245,52 @@ abstract class AbstractItemProvider
*/
protected function addItemsFromFolder(array $result, $fieldName, array $items)
{
if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
|| !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolderConfig']['folder'])
|| !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolderConfig']['folder'])
) {
return $items;
}
$fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
$fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
if ($fileFolder === '') {
$tableName = $result['tableName'];
$fileFolderConfig = $result['processedTca']['columns'][$fieldName]['config']['fileFolderConfig'];
$fileFolderTSconfig = $result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['config.']['fileFolderConfig.'] ?? [];
if (is_array($fileFolderTSconfig) && $fileFolderTSconfig !== []) {
if ($fileFolderTSconfig['folder'] ?? false) {
$fileFolderConfig['folder'] = $fileFolderTSconfig['folder'];
}
if (isset($fileFolderTSconfig['allowedExtensions'])) {
$fileFolderConfig['allowedExtensions'] = $fileFolderTSconfig['allowedExtensions'];
}
if (isset($fileFolderTSconfig['depth'])) {
$fileFolderConfig['depth'] = (int)$fileFolderTSconfig['depth'];
}
}
$folderRaw = $fileFolderConfig['folder'];
$folder = GeneralUtility::getFileAbsFileName($folderRaw);
if ($folder === '') {
throw new \RuntimeException(
'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
'Invalid folder given for item processing: ' . $folderRaw . ' for table ' . $tableName . ', field ' . $fieldName,
1479399227
);
}
$fileFolder = rtrim($fileFolder, '/') . '/';
$folder = rtrim($folder, '/') . '/';
if (@is_dir($fileFolder)) {
$fileExtensionList = '';
if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
&& is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
) {
$fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
if (@is_dir($folder)) {
$allowedExtensions = '';
if (!empty($fileFolderConfig['allowedExtensions']) && is_string($fileFolderConfig['allowedExtensions'])) {
$allowedExtensions = $fileFolderConfig['allowedExtensions'];
}
$recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
? MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
$depth = isset($fileFolderConfig['depth'])
? MathUtility::forceIntegerInRange($fileFolderConfig['depth'], 0, 99)
: 99;
$fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, false, $recursionLevels);
$fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
$fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $folder, $allowedExtensions, false, $depth);
$fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $folder);
foreach ($fileArray as $fileReference) {
$fileInformation = pathinfo($fileReference);
$icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
? $fileFolder . $fileReference
? $folder . $fileReference
: '';
$items[] = [
$fileReference,
......
......@@ -46,7 +46,7 @@ class FormEngineUtility
'input' => ['size', 'max', 'readOnly'],
'text' => ['cols', 'rows', 'wrap', 'max', 'readOnly'],
'check' => ['cols', 'readOnly'],
'select' => ['size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'treeConfig'],
'select' => ['size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'treeConfig', 'fileFolderConfig'],
'group' => ['size', 'autoSizeMax', 'max_size', 'maxitems', 'minitems', 'readOnly'],
'inline' => ['appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label', 'readOnly'],
'imageManipulation' => ['ratios', 'cropVariants']
......
......@@ -389,7 +389,7 @@ class TcaSelectItemsTest extends UnitTestCase
*/
public function addDataAddsFileItemsWithConfiguredFileFolder(): void
{
$directory = StringUtility::getUniqueId('test-') . '/';
$directory = Environment::getVarPath() . '/' . StringUtility::getUniqueId('test-') . '/';
$input = [
'tableName' => 'aTable',
'databaseRow' => [],
......@@ -399,35 +399,115 @@ class TcaSelectItemsTest extends UnitTestCase
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
// use absolute path here to avoid fallback to public path as prefix
'fileFolder' => Environment::getVarPath() . '/' . $directory,
'fileFolder_extList' => 'gif',
'fileFolder_recursions' => 1,
'fileFolderConfig' => [
'folder' => $directory,
'allowedExtensions' => 'gif',
'depth' => 1
]
],
],
],
],
];
mkdir(Environment::getVarPath() . '/' . $directory);
$this->testFilesToDelete[] = Environment::getVarPath() . '/' . $directory;
touch(Environment::getVarPath() . '/' . $directory . 'anImage.gif');
touch(Environment::getVarPath() . '/' . $directory . 'aFile.txt');
mkdir(Environment::getVarPath() . '/' . $directory . '/subdir');
touch(Environment::getVarPath() . '/' . $directory . '/subdir/anotherImage.gif');
mkdir($directory);
$this->testFilesToDelete[] = $directory;
touch($directory . 'anImage.gif');
touch($directory . 'aFile.txt');
mkdir($directory . '/subdir');
touch($directory . '/subdir/anotherImage.gif');
mkdir($directory . '/subdir/subsubdir');
touch($directory . '/subdir/subsubdir/anotherImage.gif');
$expectedItems = [
0 => [
0 => 'anImage.gif',
1 => 'anImage.gif',
2 => Environment::getVarPath() . '/' . $directory . 'anImage.gif',
2 => $directory . 'anImage.gif',
3 => null,
4 => null,
],
1 => [
0 => 'subdir/anotherImage.gif',
1 => 'subdir/anotherImage.gif',
2 => Environment::getVarPath() . '/' . $directory . 'subdir/anotherImage.gif',
2 => $directory . 'subdir/anotherImage.gif',
3 => null,
4 => null,
],
];
$result = (new TcaSelectItems())->addData($input);
self::assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
}
/**
* @test
*/
public function addDataAddsFileItemsWithOverwrittenFileFolder(): void
{
$directory = Environment::getVarPath() . '/' . StringUtility::getUniqueId('test-') . '/';
$overriddenDirectory = Environment::getVarPath() . '/' . StringUtility::getUniqueId('test-overridden-') . '/';
$input = [
'tableName' => 'aTable',
'databaseRow' => [],
'processedTca' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'fileFolderConfig' => [
'folder' => $directory,
'allowedExtensions' => 'gif',
'depth' => 1
]
],
],
],
],
'pageTsConfig' => [
'TCEFORM.' => [
'aTable.' => [
'aField.' => [
'config.' => [
'fileFolderConfig.' => [
'folder' => $overriddenDirectory,
'allowedExtensions' => 'svg',
'depth' => 0,
]
],
],
],
],
],
];
mkdir($directory);
$this->testFilesToDelete[] = $directory;
touch($directory . 'anImage.gif');
touch($directory . 'aFile.txt');
touch($directory . 'aIcon.svg');
mkdir($directory . '/subdir');
touch($directory . '/subdir/anotherImage.gif');
touch($directory . '/subdir/anotherFile.txt');
touch($directory . '/subdir/anotherIcon.txt');
mkdir($overriddenDirectory);
$this->testFilesToDelete[] = $overriddenDirectory;
touch($overriddenDirectory . 'anOverriddenImage.gif');
touch($overriddenDirectory . 'anOverriddenFile.txt');
touch($overriddenDirectory . 'anOverriddenIcon.svg');
mkdir($overriddenDirectory . '/subdir');
touch($overriddenDirectory . '/subdir/anotherOverriddenImage.gif');
touch($overriddenDirectory . '/subdir/anotherOverriddenFile.txt');
touch($overriddenDirectory . '/subdir/anotherOverriddenIcon.svg');
$expectedItems = [
0 => [
0 => 'anOverriddenIcon.svg',
1 => 'anOverriddenIcon.svg',
2 => $overriddenDirectory . 'anOverriddenIcon.svg',
3 => null,
4 => null,
],
......@@ -452,7 +532,9 @@ class TcaSelectItemsTest extends UnitTestCase
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'fileFolder' => 'EXT:non_existing/Resources/Public/',
'fileFolderConfig' => [
'folder' => 'EXT:non_existing/Resources/Public/'
],
],
],
],
......
......@@ -61,6 +61,7 @@ class TcaMigration
$tca = $this->migrateLanguageFieldToTcaTypeLanguage($tca);
$tca = $this->migrateSpecialLanguagesToTcaTypeLanguage($tca);
$tca = $this->removeShowRemovedLocalizationRecords($tca);
$tca = $this->migrateFileFolderConfiguration($tca);
return $tca;
}
......@@ -436,4 +437,47 @@ class TcaMigration
return $tca;
}
/**
* Moves the "fileFolder" configuration of TCA columns type=select
* into sub array "fileFolderConfig", while renaming those options.
*
* @param array $tca
* @return array
*/
protected function migrateFileFolderConfiguration(array $tca): array
{
foreach ($tca as $table => &$tableDefinition) {
if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
continue;
}
foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
if ((string)($fieldConfig['config']['type'] ?? '') !== 'select'
|| !isset($fieldConfig['config']['fileFolder'])
) {
continue;
}
$fieldConfig['config']['fileFolderConfig'] = [
'folder' => $fieldConfig['config']['fileFolder']
];
unset($fieldConfig['config']['fileFolder']);
if (isset($fieldConfig['config']['fileFolder_extList'])) {
$fieldConfig['config']['fileFolderConfig']['allowedExtensions'] = $fieldConfig['config']['fileFolder_extList'];
unset($fieldConfig['config']['fileFolder_extList']);
}
if (isset($fieldConfig['config']['fileFolder_recursions'])) {
$fieldConfig['config']['fileFolderConfig']['depth'] = $fieldConfig['config']['fileFolder_recursions'];
unset($fieldConfig['config']['fileFolder_recursions']);
}
$this->messages[] = 'The TCA field \'' . $fieldName . '\' of table \'' . $table . '\' is '
. 'defined as type \'select\' with the \'fileFolder\' configuration option set. To streamline '
. 'the configuration, all \'fileFolder\' related configuration options were moved into a '
. 'dedicated sub array \'fileFolderConfig\', while \'fileFolder\' is now just \'folder\' and '
. 'the other options have been renamed to \'allowedExtensions\' and \'depth\'. '
. 'The TCA configuration should be adjusted accordingly.';
}
}
return $tca;
}
}
.. include:: ../../Includes.txt
=====================================================================
Feature: #94406 - Override fileFolder TCA configuration with TSconfig
=====================================================================
See :issue:`94406`
Description
===========
The special `fileFolder configuration options <https://docs.typo3.org/m/typo3/reference-tca/master/en-us/ColumnsConfig/Type/Select/Properties/FileFolder.html#filefolder>`__
for TCA columns of type `select` can be used to fill a select field with files
(images / icons) from a defined folder. This is really handy, e.g. for selecting
predefined icons from a corporate icon set. However, in installations with
multiple sites, such icon sets usually differ from site to site.
Therefore, the `fileFolder` configuration can now be overridden with page
TSconfig, allowing administrators to easily handle those situations by e.g.
using different folders or allowing different file extensions, per site.
To streamline both, the TCA configuration and the corresponding overrides,
the `fileFolder` configuration options have been moved into a dedicated sub
array :php:`fileFolderConfig`, while the `fileFolder` option has been renamed
to just `folder`, `fileFolder_extList` to `allowedExtensions` and
`fileFolder_recursions` to `depth`. A TCA migration wizard is available,
showing where adjustments have to take place.
.. code-block:: php
// Before
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'fileFolder' => 'EXT:my_ext/Resources/Public/Icons',
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
// After
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'fileFolderConfig' => [
'folder' => 'EXT:styleguide/Resources/Public/Icons',
'allowedExtensions' => 'svg',
'depth' => 1,
]
]
]
Thus, the following TSconfig options can be used to overriding their
TCA counterpart:
.. code-block:: typoscript
config.fileFolderConfig.folder
config.fileFolderConfig.allowedExtensions
config.fileFolderConfig.depth
As already known from TCEFORM, those options can be used on various levels
On table level:
.. code-block:: typoscript
TCEFORM.myTable.myField.config.fileFolderConfig.folder
On table and record type level:
.. code-block:: typoscript
TCEFORM.myTable.myFiled.types.myType.config.fileFolderConfig.folder
On flex form field level:
.. code-block:: typoscript
TCEFORM.myTable.pi_flexform.my_ext_pi1.sDEF.myField.config.fileFolderConfig.folder
.. note::
Except `config.fileFolderConfig.folder`, the new options can not
only be used to override an existing property, but also to define
one, which has not yet been configured in TCA.
Impact
======
It's now possible to override the TCA `fileFolder` configuration options
with page TSconfig, allowing administrators to manipulate the available
items on a page basis.
The `fileFolder` TCA confgiruation is furthermore streamlined and now
encapsulated in a dedicated sub array :php:`fileFolderConfig`.
.. index:: Backend, TCA, TSConfig, ext:backend
......@@ -891,4 +891,155 @@ class TcaMigrationTest extends UnitTestCase
$subject = new TcaMigration();
self::assertEquals($expected, $subject->migrate($input));
}
/**
* @test
*/
public function fileFolderConfigurationIsMigrated(): void
{
$input = [
'aTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolder' => 'EXT:styleguide/Resources/Public/Icons',
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
'bTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolder' => 'EXT:styleguide/Resources/Public/Icons',
]
]
]
],
'cTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolder' => '',
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
'dTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
'fileFolder' => 'EXT:styleguide/Resources/Public/Icons',
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
'eTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
];
$expected = [
'aTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolderConfig' => [
'folder' => 'EXT:styleguide/Resources/Public/Icons',
'allowedExtensions' => 'svg',
'depth' => 1
]
]
]
]
],
'bTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolderConfig' => [
'folder' => 'EXT:styleguide/Resources/Public/Icons',
]
]
]
]
],
'cTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolderConfig' => [
'folder' => '',
'allowedExtensions' => 'svg',
'depth' => 1
]
]
]
]
],
'dTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
'fileFolder' => 'EXT:styleguide/Resources/Public/Icons',
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
'eTable' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'fileFolder_extList' => 'svg',
'fileFolder_recursions' => 1,
]
]
]
],
];
$subject = new TcaMigration();
self::assertEquals($expected, $subject->migrate($input));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment