TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
|
||||
|
||||
use TYPO3\CMS\Backend\Form\FormDataCompiler;
|
||||
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaInputPlaceholderRecord;
|
||||
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Resolve placeholders for fields of type input or text. The placeholder value
|
||||
* in the processedTca section of the result will be replaced with the resolved
|
||||
* value.
|
||||
*/
|
||||
readonly class TcaInputPlaceholders implements FormDataProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Resolve placeholders for input/email/text fields. Placeholders that are simple
|
||||
* strings will be returned unmodified. Placeholders beginning with __row are
|
||||
* being resolved, possibly traversing multiple tables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function addData(array $result)
|
||||
{
|
||||
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
|
||||
// Placeholders are only valid for input-like and text-like fields.
|
||||
if (!isset($fieldConfig['config']['placeholder'], $fieldConfig['config']['type'])
|
||||
|| (
|
||||
$fieldConfig['config']['type'] !== 'input'
|
||||
&& $fieldConfig['config']['type'] !== 'text'
|
||||
&& $fieldConfig['config']['type'] !== 'number'
|
||||
&& $fieldConfig['config']['type'] !== 'email'
|
||||
&& $fieldConfig['config']['type'] !== 'link'
|
||||
&& $fieldConfig['config']['type'] !== 'password'
|
||||
&& $fieldConfig['config']['type'] !== 'datetime'
|
||||
&& $fieldConfig['config']['type'] !== 'color'
|
||||
&& $fieldConfig['config']['type'] !== 'json'
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve __row|field type placeholders
|
||||
if (str_starts_with((string)$fieldConfig['config']['placeholder'], '__row|')) {
|
||||
// split field names into array and remove the __row indicator
|
||||
$fieldNameArray = array_slice(
|
||||
GeneralUtility::trimExplode('|', $fieldConfig['config']['placeholder'], true),
|
||||
1
|
||||
);
|
||||
$result['processedTca']['columns'][$fieldName]['config']['placeholder'] = $this->getPlaceholderValue($fieldNameArray, $result);
|
||||
} elseif (!empty($fieldConfig['config']['placeholder'])) {
|
||||
// Resolve placeholders from language files
|
||||
$result['processedTca']['columns'][$fieldName]['config']['placeholder'] = $this->getLanguageService()->sL($fieldConfig['config']['placeholder']);
|
||||
}
|
||||
|
||||
// Remove empty placeholders
|
||||
if (empty($result['processedTca']['columns'][$fieldName]['config']['placeholder'])) {
|
||||
unset($result['processedTca']['columns'][$fieldName]['config']['placeholder']);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively resolve the placeholder value. A placeholder string with a
|
||||
* syntax of __row|field1|field2|field3 will be recursively resolved to a
|
||||
* final value.
|
||||
*
|
||||
* @param array $fieldNameArray
|
||||
* @param array $result
|
||||
* @param int $recursionLevel
|
||||
* @return string
|
||||
*/
|
||||
protected function getPlaceholderValue($fieldNameArray, $result, $recursionLevel = 0)
|
||||
{
|
||||
if ($recursionLevel > 99) {
|
||||
// This should not happen, treat as misconfiguration
|
||||
return '';
|
||||
}
|
||||
|
||||
$fieldName = array_shift($fieldNameArray);
|
||||
|
||||
// Skip if a defined field was actually not present in the database row
|
||||
// Using array_key_exists here, since NULL values are valid as well.
|
||||
if (!array_key_exists($fieldName, $result['databaseRow'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = $result['databaseRow'][$fieldName];
|
||||
|
||||
if (!isset($result['processedTca']['columns'][$fieldName]['config'])
|
||||
|| !is_array($result['processedTca']['columns'][$fieldName]['config'])
|
||||
) {
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
$fieldConfig = $result['processedTca']['columns'][$fieldName]['config'];
|
||||
|
||||
switch ($fieldConfig['type']) {
|
||||
case 'select':
|
||||
case 'category':
|
||||
// The FormDataProviders already resolved the select items to an array of uids,
|
||||
// filter out empty values that occur when no related record has been selected.
|
||||
$possibleUids = array_filter($value);
|
||||
$foreignTableName = $fieldConfig['foreign_table'] ?? '';
|
||||
break;
|
||||
case 'group':
|
||||
$possibleUids = $this->getRelatedGroupFieldUids($fieldConfig, $value);
|
||||
$foreignTableName = $this->getAllowedTableForGroupField($fieldConfig);
|
||||
break;
|
||||
case 'inline':
|
||||
case 'file':
|
||||
$possibleUids = array_filter(GeneralUtility::trimExplode(',', $value, true));
|
||||
$foreignTableName = $fieldConfig['foreign_table'];
|
||||
break;
|
||||
default:
|
||||
$possibleUids = [];
|
||||
$foreignTableName = '';
|
||||
}
|
||||
|
||||
if (!empty($possibleUids) && !empty($fieldNameArray)) {
|
||||
if (count($possibleUids) > 1
|
||||
&& $result['tcaSchemata']->get($foreignTableName)->isLanguageAware()
|
||||
&& isset($result['currentSysLanguage'])
|
||||
) {
|
||||
$possibleUids = $this->getPossibleUidsByCurrentSysLanguage($result, $possibleUids, $foreignTableName, $result['currentSysLanguage']);
|
||||
}
|
||||
$relatedFormData = $this->getRelatedFormData($result, $foreignTableName, $possibleUids[0], $fieldNameArray[0]);
|
||||
if ($result['tcaSchemata']->get($result['tableName'])->isLanguageAware()
|
||||
&& isset($result['databaseRow'][$result['tcaSchemata']->get($result['tableName'])->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName()])
|
||||
) {
|
||||
$relatedFormData['currentSysLanguage'] = $result['databaseRow'][$result['tcaSchemata']->get($result['tableName'])->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName()];
|
||||
}
|
||||
$value = $this->getPlaceholderValue($fieldNameArray, $relatedFormData, $recursionLevel + 1);
|
||||
}
|
||||
|
||||
if ($recursionLevel === 0 && is_array($value)) {
|
||||
$value = implode(', ', $value);
|
||||
}
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a formdata result set based on the tablename and record uid.
|
||||
*
|
||||
* @param string $tableName Name of the table for which to compile formdata
|
||||
* @param int $uid UID of the record for which to compile the formdata
|
||||
* @param string $columnToProcess The column that is required from the record
|
||||
* @return array The compiled formdata
|
||||
*/
|
||||
protected function getRelatedFormData(array $result, $tableName, $uid, $columnToProcess)
|
||||
{
|
||||
$fakeDataInput = [
|
||||
'request' => $result['request'],
|
||||
'command' => 'edit',
|
||||
'vanillaUid' => (int)$uid,
|
||||
'tableName' => $tableName,
|
||||
'inlineCompileExistingChildren' => false,
|
||||
'columnsToProcess' => [$columnToProcess],
|
||||
// pass through schemata as they are immutable once they are set
|
||||
'tcaSchemata' => $result['tcaSchemata'],
|
||||
// pass through fullTca as it is immutable once set
|
||||
'fullTca' => $result['fullTca'],
|
||||
];
|
||||
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class);
|
||||
return $formDataCompiler->compile($fakeDataInput, GeneralUtility::makeInstance(TcaInputPlaceholderRecord::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return uids of related records for group type fields. Uids consisting of
|
||||
* multiple parts like [table]_[uid]|[title] will be reduced to integers and
|
||||
* validated against the allowed table. Uids without a table prefix are
|
||||
* accepted in any case.
|
||||
*
|
||||
* @param array $fieldConfig TCA "config" section for the group type field.
|
||||
* @param array $value Related group field values prepared by TcaGroup data provider
|
||||
*/
|
||||
protected function getRelatedGroupFieldUids(array $fieldConfig, $value): array
|
||||
{
|
||||
$relatedUids = [];
|
||||
$allowedTable = $this->getAllowedTableForGroupField($fieldConfig);
|
||||
|
||||
// Skip if it's not a resolvable foreign table
|
||||
if (!$allowedTable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Related group values have been prepared by TcaGroup data provider, an array is expected here
|
||||
foreach ($value as $singleValue) {
|
||||
$relatedUids[] = $singleValue['uid'];
|
||||
}
|
||||
|
||||
return $relatedUids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will read the "allowed" value from the given field configuration
|
||||
* and returns FALSE if none or more than one has been defined.
|
||||
* Otherwise the name of the allowed table will be returned.
|
||||
*
|
||||
* @param array $fieldConfig TCA "config" section for the group type field.
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function getAllowedTableForGroupField(array $fieldConfig)
|
||||
{
|
||||
$allowedTable = false;
|
||||
|
||||
$allowedTables = GeneralUtility::trimExplode(',', $fieldConfig['allowed'], true);
|
||||
if (count($allowedTables) === 1) {
|
||||
$allowedTable = $allowedTables[0];
|
||||
}
|
||||
|
||||
return $allowedTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* E.g. sys_file is not translatable, thus the uid of the translation of it's metadata has to be retrieved here.
|
||||
*
|
||||
* Get the uid of e.g. a file metadata entry for a given sys_language_uid and the possible translated data.
|
||||
* If there is no translation available, return the uid of default language.
|
||||
* If there is no value at all, return the "possible uids".
|
||||
*
|
||||
* @param array $result
|
||||
* @param array $possibleUids
|
||||
* @param string $foreignTableName
|
||||
* @param int $currentLanguage
|
||||
* @return array
|
||||
*/
|
||||
protected function getPossibleUidsByCurrentSysLanguage(array $result, array $possibleUids, $foreignTableName, $currentLanguage)
|
||||
{
|
||||
$languageField = $result['tcaSchemata']->get($foreignTableName)->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($foreignTableName);
|
||||
$possibleRecords = $queryBuilder->select('uid', $languageField)
|
||||
->from($foreignTableName)
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($possibleUids, Connection::PARAM_INT_ARRAY)
|
||||
),
|
||||
$queryBuilder->expr()->in(
|
||||
$languageField,
|
||||
$queryBuilder->createNamedParameter([$currentLanguage, 0], Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->groupBy($languageField, 'uid')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
if (!empty($possibleRecords)) {
|
||||
// Either only one record or first record matches language
|
||||
if (count($possibleRecords) === 1
|
||||
|| (int)$possibleRecords[0][$languageField] === (int)$currentLanguage
|
||||
) {
|
||||
return [$possibleRecords[0]['uid']];
|
||||
}
|
||||
|
||||
// Language of second record matches language
|
||||
return [$possibleRecords[1]['uid']];
|
||||
}
|
||||
|
||||
return $possibleUids;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user