174 lines
5.8 KiB
PHP
174 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* 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\Core\Domain;
|
|
|
|
use Psr\Container\ContainerInterface;
|
|
use TYPO3\CMS\Core\Domain\Exception\FlexFieldPropertyException;
|
|
use TYPO3\CMS\Core\Domain\Exception\FlexFieldPropertyNotFoundException;
|
|
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
|
|
|
/**
|
|
* Represents a record's flex form field values.
|
|
*
|
|
* @internal not part of public API, as this needs to be streamlined and proven
|
|
*/
|
|
class FlexFormFieldValues implements ContainerInterface, \ArrayAccess
|
|
{
|
|
public function __construct(
|
|
protected array $sheets
|
|
) {}
|
|
|
|
public function get(string $id)
|
|
{
|
|
if (!$this->has($id)) {
|
|
throw new FlexFieldPropertyNotFoundException('Flex property "' . $id . '" is not available.', 1731962637);
|
|
}
|
|
|
|
[$sheetName, $propertyPath] = $this->processId($id);
|
|
|
|
if ($sheetName === '' && $this->hasMultipleSheets()) {
|
|
// Get the sheet name for the requested property path - There is one, since has() returned true.
|
|
foreach ($this->sheets as $name => $sheet) {
|
|
if (ArrayUtility::isValidPath($sheet, $propertyPath, '.')) {
|
|
$sheetName = $name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$propertyValue = ArrayUtility::getValueByPath($this->sheets[$sheetName], $propertyPath, '.');
|
|
if (is_array($propertyValue)) {
|
|
array_walk_recursive($propertyValue, fn(mixed &$value): mixed => $value = $this->resolveRecordPropertyClosure($propertyPath, $value));
|
|
} else {
|
|
$propertyValue = $this->resolveRecordPropertyClosure($propertyPath, $propertyValue);
|
|
}
|
|
ArrayUtility::setValueByPath($this->sheets[$sheetName], $propertyPath, $propertyValue, '.');
|
|
return $propertyValue;
|
|
}
|
|
|
|
public function has(string $id): bool
|
|
{
|
|
// If there are no sheets, no value can be determined.
|
|
if ($this->sheets === []) {
|
|
return false;
|
|
}
|
|
|
|
[$sheetName, $propertyPath] = $this->processId($id);
|
|
|
|
if ($sheetName !== '' && !isset($this->sheets[$sheetName])) {
|
|
// Given sheet name does not exist
|
|
return false;
|
|
}
|
|
if ($this->hasMultipleSheets()) {
|
|
if ($sheetName !== '') {
|
|
return ArrayUtility::isValidPath($this->sheets[$sheetName], $propertyPath, '.');
|
|
}
|
|
// In case no sheet name is given, we try to execute fallback handling
|
|
// by searching for the requested $propertyPath in all sheets.
|
|
$occurences = [];
|
|
foreach ($this->sheets as $sheetName => $sheet) {
|
|
if (ArrayUtility::isValidPath($sheet, $propertyPath, '.')) {
|
|
$occurences[$sheetName] = $propertyPath;
|
|
}
|
|
}
|
|
|
|
if (count($occurences) > 1) {
|
|
// This is a special case, which we handle with an exception to create awareness for the error.
|
|
throw new FlexFieldPropertyException('Given id is ambigious since the field exists in multiple sheets and no sheet is defined.', 1731962638);
|
|
}
|
|
// Whether the requested $propertyPath name exist in a sheet
|
|
return count($occurences) === 1;
|
|
}
|
|
|
|
// Standard case, whether the $propertyPath exists in the given sheet name
|
|
return ArrayUtility::isValidPath($this->sheets[$sheetName], $propertyPath, '.');
|
|
}
|
|
|
|
public function offsetExists(mixed $offset): bool
|
|
{
|
|
return $this->has($offset);
|
|
}
|
|
|
|
public function offsetGet(mixed $offset): mixed
|
|
{
|
|
return $this->get($offset);
|
|
}
|
|
|
|
public function offsetSet(mixed $offset, mixed $value): void
|
|
{
|
|
// Not implemented
|
|
}
|
|
|
|
public function offsetUnset(mixed $offset): void
|
|
{
|
|
// Not implemented
|
|
}
|
|
|
|
/**
|
|
* Getter to be used in fluid for accessing field values
|
|
*/
|
|
public function getSheets(): array
|
|
{
|
|
return $this->sheets;
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return $this->getSheets();
|
|
}
|
|
|
|
protected function hasMultipleSheets(): bool
|
|
{
|
|
return count($this->sheets) > 1;
|
|
}
|
|
|
|
protected function processId(string $id): array
|
|
{
|
|
if (str_contains($id, '/')) {
|
|
// $id contains a sheet name
|
|
return explode('/', $id, 2);
|
|
}
|
|
if ($this->hasMultipleSheets()) {
|
|
// $id does not contain a sheet name while there are multiple sheets. Therefore, we
|
|
// return an empty sheet name. This allows executing fallback handling in has() and get().
|
|
return ['', $id];
|
|
}
|
|
|
|
// In case the $id does not contain a sheet name, but we have a
|
|
// single sheet flex form, we fall back to this name automatically.
|
|
return [key($this->sheets), $id];
|
|
}
|
|
|
|
protected function resolveRecordPropertyClosure(string $id, mixed $propertyValue): mixed
|
|
{
|
|
if ($propertyValue instanceof RecordPropertyClosure) {
|
|
try {
|
|
$propertyValue = $propertyValue->instantiate();
|
|
} catch (\Exception $e) {
|
|
// Consumers of this method can rely on catching ContainerExceptionInterface
|
|
throw new FlexFieldPropertyException(
|
|
'An exception occurred while instantiating flex field property "' . $id . '"',
|
|
1731962735,
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
return $propertyValue;
|
|
}
|
|
}
|