Skip to content

Commit

Permalink
[FEATURE] Allow overriding fileFolder config with TSconfig
Browse files Browse the repository at this point in the history
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 <typo3@b13.com>
Tested-by: Jochen <rothjochen@gmail.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Jochen <rothjochen@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
  • Loading branch information
o-ba authored and bmack committed Jul 16, 2021
1 parent 6d956e8 commit 2f0c265
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 33 deletions.
Expand Up @@ -245,38 +245,52 @@ protected function addItemsFromSpecial(array $result, $fieldName, array $items)
*/
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,
Expand Down
Expand Up @@ -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']
Expand Down
Expand Up @@ -389,7 +389,7 @@ public function addDataKeepsIconFromItem(): void
*/
public function addDataAddsFileItemsWithConfiguredFileFolder(): void
{
$directory = StringUtility::getUniqueId('test-') . '/';
$directory = Environment::getVarPath() . '/' . StringUtility::getUniqueId('test-') . '/';
$input = [
'tableName' => 'aTable',
'databaseRow' => [],
Expand All @@ -399,35 +399,115 @@ public function addDataAddsFileItemsWithConfiguredFileFolder(): void
'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,
],
Expand All @@ -452,7 +532,9 @@ public function addDataThrowsExceptionForInvalidFileFolder(): void
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'fileFolder' => 'EXT:non_existing/Resources/Public/',
'fileFolderConfig' => [
'folder' => 'EXT:non_existing/Resources/Public/'
],
],
],
],
Expand Down
44 changes: 44 additions & 0 deletions typo3/sysext/core/Classes/Migrations/TcaMigration.php
Expand Up @@ -61,6 +61,7 @@ public function migrate(array $tca): array
$tca = $this->migrateLanguageFieldToTcaTypeLanguage($tca);
$tca = $this->migrateSpecialLanguagesToTcaTypeLanguage($tca);
$tca = $this->removeShowRemovedLocalizationRecords($tca);
$tca = $this->migrateFileFolderConfiguration($tca);

return $tca;
}
Expand Down Expand Up @@ -436,4 +437,47 @@ protected function removeShowRemovedLocalizationRecords(array $tca): array

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;
}
}
@@ -0,0 +1,100 @@
.. 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

0 comments on commit 2f0c265

Please sign in to comment.