Skip to content

Commit

Permalink
[!!!][TASK] Improve flex and TCA handling in FormEngine
Browse files Browse the repository at this point in the history
The patch adapts a series of nasty form engine areas to more solid
code. The evaluate condition code is rewritten and works much better
in flex form scenarios. The suggest wizard and svg tree are much
more solid in flex forms. The group element is rewritten
towards a better readable and easier to refactor code, dropping
method dbFileIcons(). A bunch of issues is resolved along the way.

* TCA "default" now works in flex form section container elements
* The "displayCond" parser is now strict and throws exceptions on
  invalid syntax and wrong referenced fields to help debugging
  faulty display conditions
* TCA displayCond on flex fields can now be prefixed with the
  sheet name and can reference field values from neighbor sheets
* TCA displayCond now works with flex section containers
* TCA flex section container now throw an exception if select or
  group fields configure a MM relation - this is not supported
* TCA ctrl requestUpdate field is dropped, onChange=reload is now allowed
  not only on flex form fields, but also on normal columns fields
* TCA tree now works as section container element and initializes
  correctly on new records and new containers
* GroupElement rewrite to drop dbFileIcons()
* config option maxitems now optional for type=group and type=select
  and defaults to "many items allowed"
* inline now works in "fancy" flex situations with "new" records
  by handing the final dataStructureIdentifier around
* FormEngine no longer loads extJS

Change-Id: Id1d081627529cc1502bb198389e5bd69372815cd
Resolves: #78899
Resolves: #72307
Resolves: #75646
Resolves: #76637
Resolves: #72106
Resolves: #78824
Resolves: #76793
Resolves: #68247
Resolves: #69715
Related: #78460
Related: #67198
Related: #72294
Releases: master
Reviewed-on: https://review.typo3.org/50879
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
  • Loading branch information
lolli42 authored and maddy2101 committed Jan 3, 2017
1 parent 82cc3e9 commit 38a1bc5
Show file tree
Hide file tree
Showing 90 changed files with 6,821 additions and 2,835 deletions.
163 changes: 163 additions & 0 deletions typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Backend\Controller;

/*
* 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!
*/

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Form\FormDataCompiler;
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;

/**
* Handle FormEngine flex field ajax calls
*/
class FormFlexAjaxController
{
/**
* Render a single flex form section container to add it to the DOM
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @return ResponseInterface
*/
public function containerAdd(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$queryParameters = $request->getParsedBody();

$vanillaUid = (int)$queryParameters['vanillaUid'];
$databaseRowUid = $queryParameters['databaseRowUid'];
$command = $queryParameters['command'];
$tableName = $queryParameters['tableName'];
$fieldName = $queryParameters['fieldName'];
$recordTypeValue = $queryParameters['recordTypeValue'];
$dataStructureIdentifier = json_encode($queryParameters['dataStructureIdentifier']);
$flexFormSheetName = $queryParameters['flexFormSheetName'];
$flexFormFieldName = $queryParameters['flexFormFieldName'];
$flexFormContainerName = $queryParameters['flexFormContainerName'];

// Prepare TCA and data values for a new section container using data providers
$processedTca = $GLOBALS['TCA'][$tableName];
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
$processedTca['columns'][$fieldName]['config']['ds'] = $dataStructure;
$processedTca['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier;
// Get a new unique id for this container.
$flexFormContainerIdentifier = StringUtility::getUniqueId();
$flexSectionContainerPreparation = [
'flexFormSheetName' => $flexFormSheetName,
'flexFormFieldName' => $flexFormFieldName,
'flexFormContainerName' => $flexFormContainerName,
'flexFormContainerIdentifier' => $flexFormContainerIdentifier,
];

$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$formDataCompilerInput = [
'tableName' => $tableName,
'vanillaUid' => (int)$vanillaUid,
'databaseRow' => [
'uid' => $databaseRowUid,
],
'command' => $command,
'recordTypeValue' => $recordTypeValue,
'processedTca' => $processedTca,
'flexSectionContainerPreparation' => $flexSectionContainerPreparation,
];
$formData = $formDataCompiler->compile($formDataCompilerInput);

$dataStructure = $formData['processedTca']['columns'][$fieldName]['config']['ds'];
$formData['fieldName'] = $fieldName;
$formData['flexFormDataStructureArray'] = $dataStructure['sheets'][$flexFormSheetName]['ROOT']['el'][$flexFormFieldName]['children'][$flexFormContainerIdentifier];
$formData['flexFormDataStructureIdentifier'] = $dataStructureIdentifier;
$formData['flexFormFieldName'] = $flexFormFieldName;
$formData['flexFormSheetName'] = $flexFormSheetName;
$formData['flexFormContainerName'] = $flexFormContainerName;
$formData['flexFormContainerIdentifier'] = $flexFormContainerIdentifier;
$formData['flexFormContainerElementCollapsed'] = false;

$formData['flexFormFormPrefix'] = '[data][' . $flexFormSheetName . '][lDEF]' . '[' . $flexFormFieldName . ']' . '[el]';
$formData['parameterArray']['itemFormElName'] = 'data[' . $tableName . '][' . $formData['databaseRow']['uid'] . '][' . $fieldName . ']';

// JavaScript code for event handlers:
// @todo: see if we can get rid of this - used in group elements, and also for the "reload" on type field changes
$formData['parameterArray']['fieldChangeFunc'] = [];
$formData['parameterArray']['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged('
. GeneralUtility::quoteJSvalue($tableName)
. ',' . GeneralUtility::quoteJSvalue($formData['databaseRow']['uid'])
. ',' . GeneralUtility::quoteJSvalue($fieldName)
. ',' . GeneralUtility::quoteJSvalue($formData['parameterArray']['itemFormElName'])
. ');';

// @todo: check GroupElement for usage of elementBaseName ... maybe kick that thing?
// Feed resulting form data to container structure to render HTML and other result data
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$formData['renderType'] = 'flexFormContainerContainer';
$newContainerResult = $nodeFactory->create($formData)->render();

$jsonResult = [
'html' => $newContainerResult['html'],
'scriptCall' => [],
];

if (!empty($newContainerResult['additionalJavaScriptSubmit'])) {
$additionalJavaScriptSubmit = implode('', $newContainerResult['additionalJavaScriptSubmit']);
$additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
$jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
}
foreach ($newContainerResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
$jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
}
// @todo: handle stylesheetFiles, additionalInlineLanguageLabelFiles

// @todo: copied from inline ajax handler - maybe extract to some abstract?
if (!empty($newContainerResult['requireJsModules'])) {
foreach ($newContainerResult['requireJsModules'] as $module) {
$moduleName = null;
$callback = null;
if (is_string($module)) {
// if $module is a string, no callback
$moduleName = $module;
$callback = null;
} elseif (is_array($module)) {
// if $module is an array, callback is possible
foreach ($module as $key => $value) {
$moduleName = $key;
$callback = $value;
break;
}
}
if ($moduleName !== null) {
$inlineCodeKey = $moduleName;
$javaScriptCode = 'require(["' . $moduleName . '"]';
if ($callback !== null) {
$inlineCodeKey .= sha1($callback);
$javaScriptCode .= ', ' . $callback;
}
$javaScriptCode .= ');';
$jsonResult['scriptCall'][] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
}
}
}

$response->getBody()->write(json_encode($jsonResult));

return $response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Localization\LocalizationFactory;
use TYPO3\CMS\Core\Utility\ArrayUtility;
Expand Down Expand Up @@ -75,18 +76,27 @@ public function createAction(ServerRequestInterface $request, ResponseInterface
$databaseRow = [];
$vanillaUid = (int)$inlineFirstPid;
}
$databaseRow = $this->addFlexFormDataStructurePointersFromAjaxContext($ajaxArguments, $databaseRow);

$flexDataStructureIdentifier = $this->getFlexFormDataStructureIdentifierFromAjaxContext($ajaxArguments);
$processedTca = [];
if ($flexDataStructureIdentifier) {
$processedTca = $GLOBALS['TCA'][$parent['table']];
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexDataStructureIdentifier);
$processedTca['columns'][$parentFieldName]['config']['dataStructureIdentifier'] = $flexDataStructureIdentifier;
$processedTca['columns'][$parentFieldName]['config']['ds'] = $dataStructure;
}

$formDataCompilerInputForParent = [
'vanillaUid' => $vanillaUid,
'command' => $command,
'tableName' => $parent['table'],
'databaseRow' => $databaseRow,
'processedTca' => $processedTca,
'inlineFirstPid' => $inlineFirstPid,
'columnsToProcess' => array_merge(
[$parentFieldName],
array_keys($databaseRow)
),
'columnsToProcess' => [
$parentFieldName,
],
// Do not resolve existing children, we don't need them now
'inlineResolveExistingChildren' => false,
];
Expand Down Expand Up @@ -248,18 +258,26 @@ public function detailsAction(ServerRequestInterface $request, ResponseInterface
'uid' => (int)$parent['uid'],
];

$databaseRow = $this->addFlexFormDataStructurePointersFromAjaxContext($ajaxArguments, $databaseRow);
$flexDataStructureIdentifier = $this->getFlexFormDataStructureIdentifierFromAjaxContext($ajaxArguments);
$processedTca = [];
if ($flexDataStructureIdentifier) {
$processedTca = $GLOBALS['TCA'][$parent['table']];
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexDataStructureIdentifier);
$processedTca['columns'][$parentFieldName]['config']['dataStructureIdentifier'] = $flexDataStructureIdentifier;
$processedTca['columns'][$parentFieldName]['config']['ds'] = $dataStructure;
}

$formDataCompilerInputForParent = [
'vanillaUid' => (int)$parent['uid'],
'command' => 'edit',
'tableName' => $parent['table'],
'databaseRow' => $databaseRow,
'processedTca' => $processedTca,
'inlineFirstPid' => $inlineFirstPid,
'columnsToProcess' => array_merge(
[$parentFieldName],
array_keys($databaseRow)
),
'columnsToProcess' => [
$parentFieldName
],
// @todo: still needed?
'inlineStructure' => $inlineStackProcessor->getStructure(),
// Do not resolve existing children, we don't need them now
Expand Down Expand Up @@ -639,7 +657,6 @@ protected function mergeChildResultIntoJsonResult(array $jsonResult, array $chil
foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
$jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
}
$jsonResult['scriptCall'][] = $childResult['extJSCODE'];
if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
$labels = [];
foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
Expand Down Expand Up @@ -927,33 +944,25 @@ protected function getParentConfigFromFlexForm(array $parentConfig, string $domO
}

/**
* Flexforms require additional database columns to be processed to determine the correct
* data structure to be used from a flexform. The required columns and their values are
* transmitted in the AJAX context of the request and need to be added to the fake database
* row for the inline parent.
* Inline fields within a flex form need the data structure identifier that
* specifies the specific flex form this inline element is in. Retrieve it from
* the context array.
*
* @param array $ajaxArguments The AJAX request arguments
* @param array $databaseRow The fake database row
* @return array The database row with the flexform data structure pointer columns added
* @return string Data structure identifier as json string
*/
protected function addFlexFormDataStructurePointersFromAjaxContext(array $ajaxArguments, array $databaseRow)
protected function getFlexFormDataStructureIdentifierFromAjaxContext(array $ajaxArguments)
{
if (!isset($ajaxArguments['context'])) {
return $databaseRow;
return '';
}

$context = json_decode($ajaxArguments['context'], true);
if (GeneralUtility::hmac(serialize($context['config'])) !== $context['hmac']) {
return $databaseRow;
}

if (isset($context['config']['flexDataStructurePointers'])
&& is_array($context['config']['flexDataStructurePointers'])
) {
$databaseRow = array_merge($context['config']['flexDataStructurePointers'], $databaseRow);
return '';
}

return $databaseRow;
return $context['config']['flexDataStructureIdentifier'] ?? '';
}

/**
Expand Down
Loading

0 comments on commit 38a1bc5

Please sign in to comment.