| Current Path : /var/www/surf/TYPO3/vendor/mask/mask/Classes/Definition/ |
| Current File : /var/www/surf/TYPO3/vendor/mask/mask/Classes/Definition/TableDefinitionCollection.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 MASK\Mask\Definition;
use InvalidArgumentException;
use MASK\Mask\Enumeration\FieldType;
use MASK\Mask\Utility\AffixUtility;
use MASK\Mask\Utility\FieldTypeUtility;
final class TableDefinitionCollection implements \IteratorAggregate
{
/**
* @var TableDefinition[]
*/
private array $definitions = [];
private ArrayDefinitionSorter $arrayDefinitionSorter;
private string $version = '8.1.0';
private bool $migrationDone = false;
private bool $restructuringDone = false;
public function __construct()
{
$this->arrayDefinitionSorter = new ArrayDefinitionSorter();
$this->arrayDefinitionSorter->setExcludedKeys(
[
'itemGroups',
]
);
}
public function __clone()
{
$this->definitions = array_map(function (TableDefinition $tableDefinition) {
return clone $tableDefinition;
}, $this->definitions);
}
public function getVersion(): string
{
return $this->version;
}
public function setToCurrentVersion(): void
{
$this->version = (new self())->getVersion();
}
public function getMigrationDone(): bool
{
return $this->migrationDone;
}
public function migrationDone(): void
{
$this->migrationDone = true;
}
public function isRestructuringNeeded(): bool
{
if ($this->restructuringDone) {
return false;
}
// No content elements saved yet
if (!$this->hasTable('tt_content')) {
return false;
}
$ttContentDefinition = $this->getTable('tt_content');
foreach ($ttContentDefinition->elements as $element) {
// If at least one override is set, we can infer that all are set.
if ($element->columnsOverride !== []) {
return false;
}
foreach ($element->columns as $fieldKey) {
$fieldType = $ttContentDefinition->tca->getField($fieldKey)->getFieldType();
if ($fieldType->canBeShared()) {
return true;
}
}
}
return false;
}
public function setRestructuringDone(): void
{
$this->restructuringDone = true;
}
public function addTable(TableDefinition $tableDefinition): void
{
if (!$this->hasTable($tableDefinition->table)) {
$this->definitions[$tableDefinition->table] = $tableDefinition;
}
}
public function getTable(string $table): TableDefinition
{
if ($this->hasTable($table)) {
return $this->definitions[$table];
}
throw new \OutOfBoundsException(sprintf('The table "%s" does not exist.', $table), 1628925803);
}
public function hasTable(string $table): bool
{
return isset($this->definitions[$table]);
}
/**
* @param bool $withVersion Compatibility flag. Can be set to false, in order to only get the tables array.
* @return array
*/
public function toArray(bool $withVersion = true): array
{
$tablesArray = array_merge([], ...$this->getTablesAsArray());
$tablesArray = $this->arrayDefinitionSorter->sort($tablesArray);
if (!$withVersion) {
return $tablesArray;
}
return [
'version' => $this->version,
'restructuringDone' => $this->restructuringDone,
'tables' => $tablesArray,
];
}
public function getTablesAsArray(): iterable
{
foreach ($this->definitions as $definition) {
yield [$definition->table => $definition->toArray()];
}
}
/**
* @return iterable<TableDefinition>
*/
public function getCustomTables(): iterable
{
foreach ($this->definitions as $tableDefinition) {
if (AffixUtility::hasMaskPrefix($tableDefinition->table)) {
yield $tableDefinition;
}
}
}
public static function createFromArray(array $tableDefinitionArray): TableDefinitionCollection
{
$tableDefinitionCollection = new self();
if (array_key_exists('version', $tableDefinitionArray)) {
$tableDefinitionCollection->version = $tableDefinitionArray['version'];
$tables = $tableDefinitionArray['tables'] ?? [];
} else {
// Fallback for definitions before the introduction of version.
$tableDefinitionCollection->version = '0.1.0';
$tables = $tableDefinitionArray;
}
$tableDefinitionCollection->restructuringDone = (bool)($tableDefinitionArray['restructuringDone'] ?? false);
foreach ($tables as $table => $definition) {
$tableDefinition = TableDefinition::createFromTableArray($table, $definition);
$tableDefinitionCollection->addTable($tableDefinition);
}
return $tableDefinitionCollection;
}
/**
* @return \Traversable|TableDefinition[]
*/
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->definitions);
}
/**
* Load Field
*/
public function loadField(string $table, string $fieldName): ?TcaFieldDefinition
{
if (!$this->hasTable($table)) {
return null;
}
$tableDefinition = $this->getTable($table);
if (!$tableDefinition->tca->hasField($fieldName)) {
return null;
}
return $tableDefinition->tca->getField($fieldName);
}
/**
* Load Element with all the field configurations
*/
public function loadElement(string $table, string $key): ?ElementTcaDefinition
{
// Only tt_content and pages can have elements
if (!in_array($table, ['tt_content', 'pages'])) {
return null;
}
if (!$this->hasTable($table)) {
return null;
}
$tableDefinition = $this->getTable($table);
$elements = $tableDefinition->elements;
if (!$elements->hasElement($key)) {
return null;
}
$element = $elements->getElement($key);
$tcaDefinition = new TcaDefinition();
foreach ($element->columns as $fieldKey) {
if ($tableDefinition->tca->hasField($fieldKey)) {
$availableTcaField = $tableDefinition->tca->getField($fieldKey);
if ($element->hasColumnsOverride($fieldKey)) {
$availableTcaField = $availableTcaField->mergeTca($element->getColumnsOverride($fieldKey));
}
$tcaDefinition->addField($availableTcaField);
if ($availableTcaField->hasFieldType() && $availableTcaField->getFieldType()->equals(FieldType::PALETTE)) {
$paletteFields = $this->loadInlineFields($availableTcaField->fullKey, $element->key, $element);
foreach ($paletteFields as $paletteField) {
$tcaDefinition->addField($paletteField);
}
}
}
}
return new ElementTcaDefinition($element, $tcaDefinition);
}
/**
* Loads all the inline fields of an inline-field, recursively!
* Not specifying an element key means, the parent key has to be an inline table.
*/
public function loadInlineFields(string $parentKey, string $elementKey, ?ElementDefinition $element = null): NestedTcaFieldDefinitions
{
// Load inline fields of own table.
if ($this->hasTable($parentKey)) {
$searchTable = $this->getTable($parentKey);
$searchTables = [$searchTable];
} else {
$searchTables = $this->definitions;
}
// Traverse tables and find palette.
$nestedTcaFields = new NestedTcaFieldDefinitions($elementKey);
foreach ($searchTables as $tableDefinition) {
foreach ($tableDefinition->tca as $field) {
if (!$field->hasInlineParent($elementKey) || $field->getInlineParent($elementKey) !== $parentKey) {
continue;
}
// Check if FieldType is available
if ($field->hasFieldType() && $field->getFieldType()->isParentField()) {
foreach ($this->loadInlineFields($field->fullKey, $elementKey, $element) as $inlineField) {
$field->addInlineField($inlineField);
}
}
// Merge TCA so it will be available in the Content Element Builder.
if ($element instanceof ElementDefinition && $element->hasColumnsOverride($field->fullKey)) {
$field = $field->mergeTca($element->getColumnsOverride($field->fullKey));
}
$nestedTcaFields->addField($field);
}
}
return $nestedTcaFields;
}
public function getFieldType(string $fieldKey, string $table = 'tt_content', string $elementKey = ''): FieldType
{
return FieldType::cast($this->getFieldTypeString($fieldKey, $table, $elementKey));
}
/**
* Returns the formType of a field in an element
* @internal
*/
public function getFieldTypeString(string $fieldKey, string $table = 'tt_content', string $elementKey = ''): string
{
$fieldDefinition = $this->loadField($table, $fieldKey);
if ($fieldDefinition instanceof TcaFieldDefinition) {
// If type is already known, return it.
if ($fieldDefinition->hasFieldType($elementKey)) {
return (string)$fieldDefinition->getFieldType($elementKey);
}
try {
return FieldTypeUtility::getFieldType($fieldDefinition->toArray(), $fieldDefinition->fullKey);
} catch (InvalidArgumentException $e) {
// For core fields this exception might pop up, because in older
// Mask versions no type was defined directly in the definition.
}
}
// If field could not be found in field definition, check for global TCA.
$tca = $GLOBALS['TCA'][$table]['columns'][$fieldKey] ?? [];
return FieldTypeUtility::getFieldType($tca, $fieldKey);
}
/**
* Returns type of field (tt_content or pages)
*/
public function getTableByField(string $fieldKey, string $elementKey = '', bool $excludeInlineFields = false): string
{
foreach ($this->definitions as $table) {
if ($excludeInlineFields && !in_array($table->table, ['tt_content', 'pages'], true)) {
continue;
}
if ($table->tca->hasField($fieldKey) && AffixUtility::hasMaskPrefix($table->table)) {
return $table->table;
}
foreach ($table->elements as $element) {
// If element key is set, ignore all other elements
if ($elementKey !== '' && ($elementKey !== $element->key)) {
continue;
}
if ($table->tca->hasField($fieldKey) || in_array($fieldKey, $element->columns, true)) {
return $table->table;
}
}
}
return '';
}
/**
* Returns all elements that use this field
*/
public function getElementsWhichUseField(string $key, string $table = 'tt_content'): ElementDefinitionCollection
{
$elementsInUse = new ElementDefinitionCollection($table);
if (!$this->hasTable($table)) {
return $elementsInUse;
}
$definition = $this->getTable($table);
foreach ($definition->elements as $element) {
foreach ($element->columns as $column) {
if ($column === $key) {
$elementsInUse->addElement($element);
break;
}
$fieldDefinition = $this->loadField($table, $column);
if ($fieldDefinition instanceof TcaFieldDefinition && $fieldDefinition->hasFieldType() && $fieldDefinition->getFieldType()->equals(FieldType::PALETTE)) {
foreach ($definition->palettes->getPalette($column)->showitem as $item) {
if ($item === $key) {
$elementsInUse->addElement($element);
break;
}
}
}
}
}
return $elementsInUse;
}
/**
* Returns the label of a field in an element
*/
public function getLabel(string $elementKey, string $fieldKey, string $table = 'tt_content'): string
{
return $this->getFieldPropertyByElement($elementKey, $fieldKey, 'label', $table);
}
/**
* Returns the description of a field in an element
*/
public function getDescription(string $elementKey, string $fieldKey, string $table = 'tt_content'): string
{
return $this->getFieldPropertyByElement($elementKey, $fieldKey, 'description', $table);
}
/**
* This method can find properties, which are defined in the ElementDefinition.
* These are "label" and "description" at the time being.
*/
private function getFieldPropertyByElement(string $elementKey, string $fieldKey, string $property, string $table = 'tt_content'): string
{
$validProperties = ['label', 'description'];
if (!in_array($property, $validProperties)) {
throw new InvalidArgumentException('The property ' . $property . ' is not a valid. Valid properties are: ' . implode(' ', $validProperties) . '.', 1636825949);
}
if (!$this->hasTable($table)) {
return '';
}
$tableDefinition = $this->getTable($table);
if (!$tableDefinition->tca->hasField($fieldKey)) {
return '';
}
// If this field is in a repeating field or palette, the description is in the field configuration.
$field = $tableDefinition->tca->getField($fieldKey);
if ($field->hasInlineParent()) {
if (empty($field->{$property . 'ByElement'})) {
return $field->{$property};
}
if (isset($field->{$property . 'ByElement'}[$elementKey])) {
return $field->{$property . 'ByElement'}[$elementKey];
}
}
// BC: If root field still has property defined directly, take it.
if ($field->{$property} !== '') {
return $field->{$property};
}
// Root level fields have their properties defined in according element array.
$elements = $tableDefinition->elements;
if (!$elements->hasElement($elementKey)) {
return '';
}
$element = $elements->getElement($elementKey);
if (!empty($element->columns)) {
$fieldIndex = array_search($fieldKey, $element->columns, true);
if ($fieldIndex !== false) {
return $element->{$property . 's'}[$fieldIndex] ?? '';
}
}
return '';
}
/**
* This method searches for an existing label of a multiuse field
*/
public function findFirstNonEmptyLabel(string $table, string $key): string
{
return $this->findFirstNonEmptyProperty($table, $key, 'label');
}
/**
* This method searches for an existing description of a multiuse field
*/
public function findFirstNonEmptyDescription(string $table, string $key): string
{
return $this->findFirstNonEmptyProperty($table, $key, 'description');
}
private function findFirstNonEmptyProperty(string $table, string $key, string $propertyName): string
{
if (!$this->hasTable($table)) {
return '';
}
$definition = $this->getTable($table);
$property = '';
foreach ($definition->elements as $element) {
if (in_array($key, $element->columns, true)) {
$property = $element->{$propertyName . 's'}[array_search($key, $element->columns, true)];
} else {
$field = $definition->tca->getField($key);
$property = $field->{$propertyName . 'ByElement'}[$element->key] ?? '';
}
if ($property !== '') {
break;
}
}
return $property;
}
}