292 lines
12 KiB
PHP
292 lines
12 KiB
PHP
<?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'];
|
|
}
|
|
}
|