Files
cms-backend/Classes/Form/InlineStackProcessor.php

219 lines
9.0 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\Backend\Form;
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
use TYPO3\CMS\Core\Schema\SchemaCollection;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Handle inline stack.
*
* Code related to inline elements need to know their nesting level. This class takes
* care of the according handling and can return field prefixes to be used in DOM.
*
* @internal: This class may change any time or vanish altogether
*/
readonly class InlineStackProcessor
{
/**
* Get a level from the stack and return the data.
* If the $level value is negative, this function works top-down,
* if the $level value is positive, this function works bottom-up.
* If -1 is given, the "current" - most bottom "stable" item is returned
*
* @param int $level Which level to return
* @return array|null The item of the stack at the requested level, or null if not found
*/
public function getStructureLevelFromStructure(array $structure, int $level): ?array
{
$structureCount = count($structure['stable'] ?? []);
if ($level < 0) {
// A level like -1 requests "the level before last" / "direct parent level"
return $structure['stable'][$structureCount + $level] ?? null;
}
return $structure['stable'][$level] ?? null;
}
/**
* Get the "unstable" structure item from structure stack.
*/
public function getUnstableStructureFromStructure(array $structure): array
{
if (!is_array($structure['unstable'] ?? false)) {
throw new \RuntimeException('No unstable inline structure found', 1428582655);
}
return $structure['unstable'];
}
/**
* Convert the DOM object-id of an inline container to an array.
* The object-id could look like 'data-bs-parentPageId-tx_mmftest_company-1-employees'.
* There are two keys:
* - 'stable': Containing full qualified identifiers (table, uid and field)
* - 'unstable': Containing partly filled data (e.g. only table and possibly field)
*
* @param string $domObjectId The DOM object-id
* @param SchemaCollection $schemaCollection The TCA schemata collection
*/
public function getStructureFromString(string $domObjectId, SchemaCollection $schemaCollection): array
{
$structure = [];
$unstable = [];
$vector = ['table', 'uid', 'field'];
// Substitute FlexForm addition and make parsing a bit easier
$domObjectId = str_replace('---', ':', $domObjectId);
// The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
$pattern = '/^data-(.+?)-(.+)$/';
if (preg_match($pattern, $domObjectId, $match)) {
$inlineFirstPid = $match[1];
$parts = explode('-', $match[2]);
$partsCnt = count($parts);
for ($i = 0; $i < $partsCnt; $i++) {
if ($i > 0 && $i % 3 == 0) {
// Store the TCA configuration of the table field in the stack
$unstable['config'] = [];
if ($schemaCollection->has($unstable['table'])) {
$tableSchema = $schemaCollection->get($unstable['table']);
if ($tableSchema->hasField($unstable['field'])) {
$unstable['config'] = $tableSchema->getField($unstable['field'])->getConfiguration();
}
}
// Fetch TSconfig:
$TSconfig = FormEngineUtility::getTSconfigForTableRow($unstable['table'], ['uid' => $unstable['uid'], 'pid' => $inlineFirstPid], $unstable['field']);
// Override TCA field config by TSconfig:
if (!isset($TSconfig['disabled']) || !$TSconfig['disabled']) {
$unstable['config'] = FormEngineUtility::overrideFieldConf($unstable['config'], $TSconfig);
}
// Extract FlexForm from field part (if any)
if (str_contains($unstable['field'], ':')) {
$fieldParts = GeneralUtility::trimExplode(':', $unstable['field']);
$unstable['field'] = array_shift($fieldParts);
// FlexForm parts start with data:
if (!empty($fieldParts) && $fieldParts[0] === 'data') {
$unstable['flexform'] = $fieldParts;
}
}
$structure['stable'][] = $unstable;
$unstable = [];
}
$unstable[$vector[$i % 3]] = $parts[$i];
}
$structure['unstable'] = $unstable;
}
return $structure;
}
/**
* Injects configuration via AJAX calls.
* This is used by inline ajax calls that transfer configuration options back to the stack for initialization.
*
* @param array $config Given config extracted from ajax call
* @todo: Review this construct - Why can't the ajax call fetch these data on its own and transfers it to client instead?
*/
public function addAjaxConfigurationToStructure(array $structure, array $config): array
{
$lastStableStructureKey = count($structure['stable'] ?? []) - 1;
if (empty($config) || $lastStableStructureKey < 0) {
return $structure;
}
$structure['stable'][$lastStableStructureKey]['config'] = $config;
return $structure;
}
/**
* Prefix for inline form fields
*/
public function getFormPrefixFromStructure(array $structure): string
{
$current = $this->getStructureLevelFromStructure($structure, -1);
if ($current) {
return 'data' . $this->getStructureItemNameFromStructure($structure, $current, 'Disposal_AttributeName');
}
return '';
}
/**
* DOM object-id for this inline level
*
* @param int|string $inlineFirstPid Pid of top level inline element storage or "NEW..."
*/
public function getDomObjectIdPrefixFromStructure(array $structure, int|string $inlineFirstPid): string
{
$current = $this->getStructureLevelFromStructure($structure, -1);
// If there are still more inline levels available
if ($current) {
return 'data-' . $inlineFirstPid . '-' . $this->getStructurePathFromStructure($structure);
}
return '';
}
/**
* Get the identifiers of a given depth of level, from the top of the stack to the bottom.
* An identifier looks like "<table>-<uid>-<field>".
*
* @return string The path of identifiers
*/
private function getStructurePathFromStructure(array $structure): string
{
$structureLevels = [];
$structureCount = count($structure['stable'] ?? []);
for ($i = 1; $i <= $structureCount; $i++) {
$currentStructure = $this->getStructureLevelFromStructure($structure, -$i) ?: [];
$currentItem = $this->getStructureItemNameFromStructure($structure, $currentStructure, 'Disposal_AttributeId');
array_unshift($structureLevels, $currentItem);
}
return implode('-', $structureLevels);
}
/**
* Create a name/id for usage in HTML output of a level of the structure stack to be used in form names.
*
* @param array $levelData Array of a level of the structure stack (containing the keys table, uid and field)
* @param string $disposal How the structure name is used (e.g. as <div id="..."> or <input name="..." />)
* @return string The name/id of that level, to be used for HTML output
*/
private function getStructureItemNameFromStructure(array $structure, array $levelData, string $disposal): string
{
$parts = [$levelData['table'], $levelData['uid']];
if (!empty($levelData['field'])) {
$parts[] = $levelData['field'];
}
if ($disposal === 'Disposal_AttributeName') {
// Use in name attributes
$parent = $this->getStructureLevelFromStructure($structure, -1);
if (!empty($levelData['field']) && !empty($levelData['flexform']) && $parent === $levelData) {
$parts[] = implode('][', $levelData['flexform']);
}
$name = '[' . implode('][', $parts) . ']';
} else {
// Use in object id attributes
$name = implode('-', $parts);
if (!empty($levelData['field']) && !empty($levelData['flexform'])) {
array_unshift($levelData['flexform'], $name);
$name = implode('---', $levelData['flexform']);
}
}
return $name;
}
}