351 lines
10 KiB
PHP
351 lines
10 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\DataHandling\Localization;
|
|
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
|
|
|
/**
|
|
* Entity for data-map item.
|
|
*
|
|
* @internal
|
|
*/
|
|
class DataMapItem
|
|
{
|
|
public const TYPE_PARENT = 'parent';
|
|
public const TYPE_DIRECT_CHILD = 'directChild';
|
|
public const TYPE_GRAND_CHILD = 'grandChild';
|
|
|
|
public const SCOPE_PARENT = State::STATE_PARENT;
|
|
public const SCOPE_SOURCE = State::STATE_SOURCE;
|
|
public const SCOPE_EXCLUDE = 'exclude';
|
|
|
|
protected string $tableName;
|
|
protected string|int $id;
|
|
protected int $liveId;
|
|
protected array $suggestedValues;
|
|
protected array $persistedValues;
|
|
protected array $configurationFieldNames;
|
|
protected bool $new;
|
|
protected ?string $type = null;
|
|
protected ?State $state = null;
|
|
protected string|int $language = 0;
|
|
protected string|int $parent = '';
|
|
protected string|int|null $source = null;
|
|
/** @var DataMapItem[][] */
|
|
protected array $dependencies = [];
|
|
|
|
/**
|
|
* Builds a data-map item. In addition to the constructor, the values
|
|
* for language, parent and source record pointers are assigned as well.
|
|
*/
|
|
public static function build(
|
|
string $tableName,
|
|
string|int $id,
|
|
array $suggestedValues,
|
|
array $persistedValues,
|
|
array $configurationFieldNames
|
|
): static {
|
|
$item = GeneralUtility::makeInstance(
|
|
static::class,
|
|
$tableName,
|
|
$id,
|
|
$suggestedValues,
|
|
$persistedValues,
|
|
$configurationFieldNames
|
|
);
|
|
|
|
// assign language specific settings of item
|
|
$item->language = (int)($suggestedValues[$item->getLanguageFieldName()] ?? $persistedValues[$item->getLanguageFieldName()] ?? 0);
|
|
$item->setParent($suggestedValues[$item->getParentFieldName()] ?? $persistedValues[$item->getParentFieldName()] ?? '');
|
|
if ($item->getSourceFieldName() !== null) {
|
|
$item->setSource($suggestedValues[$item->getSourceFieldName()] ?? $persistedValues[$item->getSourceFieldName()] ?? '');
|
|
}
|
|
// assign live-id of item if available
|
|
$versionSourceFieldName = $item->getVersionSourceFieldName();
|
|
if ($versionSourceFieldName !== null && !empty($persistedValues[$versionSourceFieldName])) {
|
|
$item->liveId = $persistedValues[$versionSourceFieldName];
|
|
}
|
|
return $item;
|
|
}
|
|
|
|
public function __construct(
|
|
string $tableName,
|
|
string|int $id,
|
|
array $suggestedValues,
|
|
array $persistedValues,
|
|
array $configurationFieldNames
|
|
) {
|
|
$this->tableName = $tableName;
|
|
$this->id = $id;
|
|
|
|
$this->suggestedValues = $suggestedValues;
|
|
$this->persistedValues = $persistedValues;
|
|
$this->configurationFieldNames = $configurationFieldNames;
|
|
|
|
$this->new = !MathUtility::canBeInterpretedAsInteger($id);
|
|
}
|
|
|
|
/**
|
|
* Gets the current table name of this data-map item.
|
|
*/
|
|
public function getTableName(): string
|
|
{
|
|
return $this->tableName;
|
|
}
|
|
|
|
/**
|
|
* Gets the id of this data-map item.
|
|
*/
|
|
public function getId(): string|int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getLiveId(): int|string
|
|
{
|
|
return $this->liveId ?? $this->id;
|
|
}
|
|
|
|
/**
|
|
* Gets the suggested values that were initially
|
|
* submitted as the whole data-map to the DataHandler.
|
|
*/
|
|
public function getSuggestedValues(): array
|
|
{
|
|
return $this->suggestedValues;
|
|
}
|
|
|
|
/**
|
|
* Gets the persisted values that represent the persisted state
|
|
* of the record this data-map item is a surrogate for - does only
|
|
* contain relevant field values.
|
|
*/
|
|
public function getPersistedValues(): array
|
|
{
|
|
return $this->persistedValues;
|
|
}
|
|
|
|
public function getConfigurationFieldNames(): array
|
|
{
|
|
return $this->configurationFieldNames;
|
|
}
|
|
|
|
public function getLanguageFieldName(): string
|
|
{
|
|
return $this->configurationFieldNames['language'];
|
|
}
|
|
|
|
public function getParentFieldName(): string
|
|
{
|
|
return $this->configurationFieldNames['parent'];
|
|
}
|
|
|
|
public function getSourceFieldName(): ?string
|
|
{
|
|
return $this->configurationFieldNames['source'] ?? null;
|
|
}
|
|
|
|
protected function getVersionSourceFieldName(): ?string
|
|
{
|
|
return $this->configurationFieldNames['versionSource'] ?? null;
|
|
}
|
|
|
|
public function isNew(): bool
|
|
{
|
|
return $this->new;
|
|
}
|
|
|
|
public function getType(): string
|
|
{
|
|
if ($this->type === null) {
|
|
// implicit: default language, it's a parent
|
|
if ($this->language === 0) {
|
|
$this->type = static::TYPE_PARENT;
|
|
} elseif (
|
|
// implicit: having source value different to parent value, it's a 2nd or higher level translation
|
|
$this->source !== 0
|
|
&& $this->source !== null
|
|
&& $this->source !== $this->parent
|
|
) {
|
|
$this->type = static::TYPE_GRAND_CHILD;
|
|
} else {
|
|
// implicit: otherwise, it's a 1st level translation
|
|
$this->type = static::TYPE_DIRECT_CHILD;
|
|
}
|
|
}
|
|
return $this->type;
|
|
}
|
|
|
|
public function isParentType(): bool
|
|
{
|
|
return $this->getType() === static::TYPE_PARENT;
|
|
}
|
|
|
|
public function isDirectChildType(): bool
|
|
{
|
|
return $this->getType() === static::TYPE_DIRECT_CHILD;
|
|
}
|
|
|
|
public function isGrandChildType(): bool
|
|
{
|
|
return $this->getType() === static::TYPE_GRAND_CHILD;
|
|
}
|
|
|
|
public function getState(): ?State
|
|
{
|
|
if ($this->state === null && !$this->isParentType()) {
|
|
$this->state = $this->buildState();
|
|
}
|
|
return $this->state;
|
|
}
|
|
|
|
public function getLanguage(): string|int
|
|
{
|
|
return $this->language;
|
|
}
|
|
|
|
public function setLanguage(string|int $language): void
|
|
{
|
|
$this->language = $language;
|
|
}
|
|
|
|
public function getParent(): string|int
|
|
{
|
|
return $this->parent;
|
|
}
|
|
|
|
public function setParent(string|int $parent): void
|
|
{
|
|
$this->parent = $this->extractId($parent);
|
|
}
|
|
|
|
public function getSource(): string|int|null
|
|
{
|
|
return $this->source;
|
|
}
|
|
|
|
public function setSource(string|int $source): void
|
|
{
|
|
$this->source = $this->extractId($source);
|
|
}
|
|
|
|
public function getIdForScope(string $scope): string|int
|
|
{
|
|
if (
|
|
$scope === static::SCOPE_PARENT
|
|
|| $scope === static::SCOPE_EXCLUDE
|
|
) {
|
|
return $this->getParent();
|
|
}
|
|
if ($scope === static::SCOPE_SOURCE) {
|
|
// $source is guaranteed to be non-null when SCOPE_SOURCE is applicable:
|
|
// getApplicableScopes() only includes it when getSourceFieldName() !== null,
|
|
// which means setSource() was called during build().
|
|
return $this->source;
|
|
}
|
|
throw new \RuntimeException('Invalid scope', 1486325248);
|
|
}
|
|
|
|
/**
|
|
* @return DataMapItem[][]
|
|
*/
|
|
public function getDependencies(): array
|
|
{
|
|
return $this->dependencies;
|
|
}
|
|
|
|
/**
|
|
* @param DataMapItem[][] $dependencies
|
|
*/
|
|
public function setDependencies(array $dependencies): void
|
|
{
|
|
$this->dependencies = $dependencies;
|
|
}
|
|
|
|
/**
|
|
* @return DataMapItem[]
|
|
*/
|
|
public function findDependencies(string $scope): array
|
|
{
|
|
return $this->dependencies[$scope] ?? [];
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getApplicableScopes(): array
|
|
{
|
|
$scopes = [];
|
|
if (!empty($this->getSourceFieldName())) {
|
|
$scopes[] = static::SCOPE_SOURCE;
|
|
}
|
|
$scopes[] = static::SCOPE_PARENT;
|
|
$scopes[] = static::SCOPE_EXCLUDE;
|
|
return $scopes;
|
|
}
|
|
|
|
/**
|
|
* Extracts real id from provided id-value, which can either be a real
|
|
* integer value, a 'NEW...' id, or a combined identifier 'tt_content_13'.
|
|
*/
|
|
protected function extractId(int|string $idValue): int|string
|
|
{
|
|
if (MathUtility::canBeInterpretedAsInteger($idValue)) {
|
|
return $idValue;
|
|
}
|
|
$idValue = (string)$idValue;
|
|
if (str_starts_with($idValue, 'NEW')) {
|
|
return $idValue;
|
|
}
|
|
// @todo Handle if $tableName does not match $this->tableName
|
|
$id = BackendUtility::splitTable_Uid($idValue)[1];
|
|
return $id;
|
|
}
|
|
|
|
protected function buildState(): ?State
|
|
{
|
|
// build from persisted states
|
|
if (!$this->isNew()) {
|
|
$state = State::fromJSON(
|
|
$this->tableName,
|
|
$this->persistedValues['l10n_state'] ?? null
|
|
);
|
|
} elseif (is_string($this->suggestedValues['l10n_state'] ?? null)) {
|
|
// use provided states for a new and copied element
|
|
$state = State::fromJSON(
|
|
$this->tableName,
|
|
$this->suggestedValues['l10n_state']
|
|
);
|
|
} else {
|
|
// provide the default states
|
|
$state = State::create($this->tableName);
|
|
}
|
|
// switch "custom" to "source" state for 2nd level translations
|
|
if ($this->isNew() && $this->isGrandChildType()) {
|
|
$state->updateStates(State::STATE_CUSTOM, State::STATE_SOURCE);
|
|
}
|
|
// apply any provided updates to the states
|
|
if (is_array($this->suggestedValues['l10n_state'] ?? null)) {
|
|
$state->update($this->suggestedValues['l10n_state']);
|
|
}
|
|
return $state;
|
|
}
|
|
}
|