Skip to content

Commit

Permalink
[FEATURE] Support flexform sheets and array mappings
Browse files Browse the repository at this point in the history
You can now map from or into arrays using the . as path segments
(like fluid). This allows also using sheets in flexform.
Prepend sheet name and a dot in dataPath (sDEF is automatically
appended).

Resolves: #540
Release: 12.0
  • Loading branch information
opi99 committed Oct 16, 2023
1 parent f35ec76 commit f32c4f2
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 110 deletions.
100 changes: 4 additions & 96 deletions Classes/Configuration/FlexForm/FlexFormTools8.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,104 +135,12 @@ public function parseDataStructureByIdentifier(string $identifier): array
return parent::parseDataStructureByIdentifier($identifier);
}

/**
* Handler for Flex Forms
*
* @param string $table The table name of the record
* @param string $field The field name of the flexform field to work on
* @param array $row The record data array
* @param object $callBackObj Object in which the call back function is located
* @param string $callBackMethod_value Method name of call back function in object for values
* @return bool|string true on success, string if error happened (error string returned)
*/
// phpcs:disable Generic.Metrics.CyclomaticComplexity
public function traverseFlexFormXMLData($table, $field, $row, $callBackObj, $callBackMethod_value)
public function prepareFlexform(array $dataStructure): array
{
// phpcs:enable
if (!is_array($GLOBALS['TCA'][$table]) || !is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
return 'TCA table/field was not defined.';
}
$this->callBackObj = $callBackObj;

// Get data structure. The methods may throw various exceptions, with some of them being
// ok in certain scenarios, for instance on new record rows. Those are ok to "eat" here
// and substitute with a dummy DS.
$dataStructureArray = [
'sheets' => [
'sDEF' => [
'ROOT' => [
'el' => [],
],
],
],
];
try {
$dataStructureIdentifier = $this->getDataStructureIdentifier($GLOBALS['TCA'][$table]['columns'][$field], $table, $field, $row);
$dataStructureArray = $this->parseDataStructureByIdentifier($dataStructureIdentifier);
// phpcs:disable
} catch (InvalidParentRowException $e) {
} catch (InvalidParentRowLoopException $e) {
} catch (InvalidParentRowRootException $e) {
} catch (InvalidPointerFieldValueException $e) {
} catch (InvalidIdentifierException $e) {
}
// phpcs:enable

// Get flexform XML data
$editData = GeneralUtility::xml2array($row[$field]);
if (!is_array($editData)) {
return 'Parsing error: ' . $editData;
}
// Check if $dataStructureArray['sheets'] is indeed an array before loop or it will crash with runtime error
if (!is_array($dataStructureArray['sheets'])) {
return 'Data Structure ERROR: sheets is defined but not an array for table ' . $table . (isset($row['uid']) ? ' and uid ' . $row['uid'] : '');
}

// Language settings:
$langChildren = 0;
$langDisabled = 0;
if (isset($dataStructArray['meta'])) {
$langChildren = $dataStructArray['meta']['langChildren'] ? 1 : 0;
$langDisabled = $dataStructArray['meta']['langDisable'] ? 1 : 0;
}

// Empty or invalid <meta>
if (!isset($editData['meta']) || !is_array($editData['meta'])) {
$editData['meta'] = [];
}
$editData['meta']['currentLangId'] = [];
$languages = TemplaVoilaUtility::getAvailableLanguages(0, false);
foreach ($languages as $lInfo) {
$editData['meta']['currentLangId'][] = $lInfo['ISOcode'];
}
$editData['meta']['currentLangId'] = array_unique($editData['meta']['currentLangId']);
if ($langChildren || $langDisabled) {
$lKeys = ['DEF'];
} else {
$lKeys = $editData['meta']['currentLangId'];
}

// Traverse languages:
foreach ($lKeys as $lKey) {
foreach ($dataStructureArray['sheets'] as $sheetKey => $sheetData) {
// Render sheet:
if (is_array($sheetData['ROOT']) && is_array($sheetData['ROOT']['el'])) {
$lang = 'l' . $lKey;
$dataStructure = $this->ensureDefaultSheet($dataStructure);
$dataStructure = $this->resolveFileDirectives($dataStructure);

$PA['vKeys'] = $langChildren && !$langDisabled ? $editData['meta']['currentLangId'] : ['DEF'];
$PA['lKey'] = $lang;
$PA['callBackMethod_value'] = $callBackMethod_value;
$PA['table'] = $table;
$PA['field'] = $field;
$PA['uid'] = $row['uid'];
// Render flexform:
$this->traverseFlexFormXMLData_recurse($sheetData['ROOT']['el'], $editData['data'][$sheetKey][$lang] ?? null, $PA, 'data/' . $sheetKey . '/' . $lang);
} else {
return 'Data Structure ERROR: No ROOT element found for sheet "' . $sheetKey . '".';
}
}
}
return true;
return $dataStructure;
}

/**
Expand Down
15 changes: 6 additions & 9 deletions Classes/Controller/Frontend/FrontendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tvp\TemplaVoilaPlus\Controller\Frontend;

use Tvp\TemplaVoilaPlus\Configuration\FlexForm\FlexFormTools8;
use Tvp\TemplaVoilaPlus\Domain\Model\Configuration\DataConfiguration;
use Tvp\TemplaVoilaPlus\Domain\Model\Configuration\MappingConfiguration;
use Tvp\TemplaVoilaPlus\Domain\Model\Configuration\TemplateConfiguration;
Expand Down Expand Up @@ -170,6 +171,7 @@ public function renderElement($row, $table, array $conf)
if (is_string($flexformData)) {
throw new \Exception('Could not load flex data: "' . $flexformData . '"');
}

$flexformValues = $this->getFlexformData($dataStructure, $flexformData);

/** @TODO
Expand Down Expand Up @@ -240,24 +242,19 @@ public function getFlexformData(DataConfiguration $dataStructure, array $flexfor
{
$flexformValues = [];

/** @TODO sheet selection */
$sheet = 'sDEF';

/** @TODO This is only correct, if there are no sheets defined */
/** We should look forward to define at minimum the sDEF default sheet */
$dataStruct = $dataStructure->getDataStructure();

/** @TODO Language selection */
$lKey = 'lDEF';
$vKey = 'vDEF';

$flexformLkeyValues = [];
if (isset($flexformData['data'][$sheet][$lKey]) && is_array($flexformData['data'][$sheet][$lKey])) {
$flexformLkeyValues = $flexformData['data'][$sheet][$lKey];
foreach ($dataStruct['sheets'] as $sheetKey => $sheetConfig) {
if (isset($flexformData['data'][$sheetKey])) {
$flexformValues[$sheetKey] = $this->processDataValues($flexformData['data'][$sheetKey][$lKey], $sheetConfig['ROOT']['el'], $vKey);
}
}

$flexformValues = $this->processDataValues($flexformLkeyValues, $dataStruct['ROOT']['el'], $vKey);

return $flexformValues;
}

Expand Down
3 changes: 3 additions & 0 deletions Classes/Handler/Configuration/DataConfigurationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

use Symfony\Component\Finder\SplFileInfo;
use Tvp\TemplaVoilaPlus\Configuration\FlexForm\FlexFormTools8;
use Tvp\TemplaVoilaPlus\Domain\Model\Configuration\AbstractConfiguration;
use Tvp\TemplaVoilaPlus\Domain\Model\Configuration\DataConfiguration;

Expand All @@ -29,6 +30,8 @@ public function createConfigurationFromConfigurationArray(array $dataStructure,
{
$dataConfiguration = new DataConfiguration($identifier, $this->place, $this, $file);

$dataStructure = (new FlexFormTools8())->prepareFlexform($dataStructure);

// Read title from XML file and set, if not empty or ROOT
if (
!empty($dataStructure['meta']['title'])
Expand Down
16 changes: 11 additions & 5 deletions Classes/Handler/Mapping/DefaultMappingHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Tvp\TemplaVoilaPlus\Utility\RecordFalUtility;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
Expand All @@ -41,7 +42,6 @@ public function process(MappingConfiguration $mappingConfiguration, array $flexf
$containerInstructions = $mappingConfiguration->getMappingToTemplate();

/** @TODO $table, $row are more global vars, they are given from function to function */

$processedMapping = $this->processContainer($flexformData, $table, $row, $containerInstructions);

return $processedMapping;
Expand Down Expand Up @@ -69,8 +69,12 @@ public function valueProcessing(array $instructions, array $flexformData, string
}
break;
case 'flexform':
if (isset($flexformData[$instructions['dataPath']])) {
$processedValue = $flexformData[$instructions['dataPath']] ?? '';
$path = str_getcsv($instructions['dataPath'], '.');
if (count($path) === 1) {
array_unshift($path, 'sDEF');
}
if (ArrayUtility::isValidPath($flexformData, $path)) {
$processedValue = ArrayUtility::getValueByPath($flexformData, $path);
}
break;
case 'typoscriptObjectPath':
Expand Down Expand Up @@ -172,7 +176,8 @@ protected function processRepeatable(array $flexformData, string $table, array $
{
$postprocessedValue = [];
foreach ($flexformData as $key => $preProcessedValue) {
$postprocessedValue[$key] = $this->processContainer($preProcessedValue, $table, $row, $containerInstructions);
$processedValue = $this->processContainer($preProcessedValue, $table, $row, $containerInstructions);
$postprocessedValue = ArrayUtility::setValueByPath($postprocessedValue, $templateFieldName, $processedValue, '.');
}
return $postprocessedValue;
}
Expand All @@ -181,7 +186,8 @@ protected function processContainer(array $flexformData, string $table, array $r
{
$postprocessedValue = [];
foreach ($containerInstructions as $templateFieldName => $instructions) {
$postprocessedValue[$templateFieldName] = $this->valueProcessing($instructions, $flexformData, $table, $row);
$processedValue = $this->valueProcessing($instructions, $flexformData, $table, $row);
$postprocessedValue = ArrayUtility::setValueByPath($postprocessedValue, $templateFieldName, $processedValue, '.');
}
return $postprocessedValue;
}
Expand Down

0 comments on commit f32c4f2

Please sign in to comment.