Commit 4e984739 authored by Christian Kuhn's avatar Christian Kuhn
Browse files

[BUGFIX] FormEngine: Custom functions

The patch adds a .rst file to document that the "low end" functions
within FormEngine (type=user, userFunc, itemsProcFunc) now receive
different data and that this may change during the development of
version 8 again.

Additionally the patch adds a change to hand over the "parent" row
for flex field processing, so itemsProcFunc for flex fields can at
least access the parent row data if needed.

Resolves: #70132
Resolves: #70467
Releases: master
Change-Id: I7319feeec8049be0e13d32418e76d48d05e9a648
Reviewed-on: https://review.typo3.org/44542

Reviewed-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Wouter Wolters's avatarWouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters's avatarWouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 59d04bfd
......@@ -98,6 +98,13 @@ class FormDataCompiler
$result = $this->formDataGroup->compile($result);
if (!is_array($result)) {
throw new \UnexpectedValueException(
'Data group provider must return array',
1446664764
);
}
$resultKeysAfterFormDataGroup = array_keys($result);
if ($resultKeysAfterFormDataGroup !== $resultKeysBeforeFormDataGroup) {
......@@ -192,6 +199,11 @@ class FormDataCompiler
// If set to TRUE, no wizards are calculated and rendered later
'disabledWizards' => false,
// Flex form field data handling is done in a separated FormDataCompiler instance. The full databaseRow
// of the record this flex form is embedded in is transferred in case features like single fields
// itemsProcFunc need to have this data at hand to do their job.
'flexParentDatabaseRow' => [],
// BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items
// @todo: better documentation of nesting behaviour and bug fixing in this area
'inlineExpandCollapseStateArray' => [],
......
......@@ -61,6 +61,9 @@ abstract class AbstractItemProvider
'row' => $result['databaseRow'],
'field' => $fieldName,
];
if (!empty($result['flexParentDatabaseRow'])) {
$processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
}
try {
GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
......
......@@ -323,6 +323,7 @@ class TcaFlexProcess implements FormDataProviderInterface
'ctrl' => [],
'columns' => [],
],
'flexParentDatabaseRow' => $result['databaseRow'],
];
if (!empty($newColumns)) {
......@@ -401,6 +402,7 @@ class TcaFlexProcess implements FormDataProviderInterface
$singleFieldName => $singleFieldConfiguration,
],
],
'flexParentDatabaseRow' => $result['databaseRow'],
];
$flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
if (array_key_exists($singleFieldName, $flexSegmentResult['databaseRow'])) {
......@@ -450,6 +452,7 @@ class TcaFlexProcess implements FormDataProviderInterface
'ctrl' => [],
'columns' => [],
],
'flexParentDatabaseRow' => $result['databaseRow'],
];
if (!empty($tcaNewColumns)) {
......
......@@ -133,6 +133,16 @@ class FormDataComplierTest extends UnitTestCase
$this->assertEquals('newData', $result['databaseRow']);
}
/**
* @test
*/
public function compileThrowsExceptionIfFormDataGroupDoesNotReturnArray()
{
$this->formDataGroupProphecy->compile(Argument::cetera())->willReturn(null);
$this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1446664764);
$this->subject->compile([]);
}
/**
* @test
*/
......
......@@ -221,6 +221,9 @@ class TcaCheckboxItemsTest extends UnitTestCase
],
],
],
'flexParentDatabaseRow' => [
'aParentDatabaseRowFieldName' => 'aParentDatabaseRowFieldValue',
],
'processedTca' => [
'columns' => [
'aField' => [
......@@ -240,6 +243,7 @@ class TcaCheckboxItemsTest extends UnitTestCase
|| $parameters['table'] !== 'aTable'
|| $parameters['row'] !== [ 'aField' => 'aValue' ]
|| $parameters['field'] !== 'aField'
|| $parameters['flexParentDatabaseRow']['aParentDatabaseRowFieldName'] !== 'aParentDatabaseRowFieldValue'
) {
throw new \UnexpectedValueException('broken', 1438604329);
}
......@@ -376,4 +380,5 @@ class TcaCheckboxItemsTest extends UnitTestCase
$this->assertSame($expected, $this->subject->addData($input));
$this->subject->addData($input);
}
}
......@@ -16,9 +16,11 @@ namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use TYPO3\CMS\Backend\Form\FormDataGroup\FlexFormSegment;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Tests\UnitTestCase;
use TYPO3\CMS\Backend\Form\FormDataProvider\TcaFlexProcess;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Lang\LanguageService;
/**
......@@ -1074,4 +1076,120 @@ class TcaFlexProcessTest extends UnitTestCase
$this->assertEquals($expected, $this->subject->addData($input));
}
/**
* @test
*/
public function addDataCallsFlexFormSegmentGroupForFieldAndAddsFlexParentDatabaseRow()
{
$input = [
'tableName' => 'aTable',
'databaseRow' => [
'aField' => [
'data' => [],
],
'pointerField' => 'aFlex',
],
'processedTca' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'flex',
'ds_pointerField' => 'pointerField',
'ds' => [
'sheets' => [
'sDEF' => [
'ROOT' => [
'type' => 'array',
'el' => [
'aFlexField' => [
'label' => 'aFlexFieldLabel',
'config' => [
'type' => 'input',
],
],
],
],
],
],
],
],
],
],
],
'pageTsConfig' => [],
];
/** @var FlexFormSegment|ObjectProphecy $dummyGroup */
$dummyGroup = $this->prophesize(FlexFormSegment::class);
GeneralUtility::addInstance(FlexFormSegment::class, $dummyGroup->reveal());
// Check array given to flex group contains databaseRow as flexParentDatabaseRow and check compile is called
$dummyGroup->compile(Argument::that(function ($result) use ($input) {
if ($result['flexParentDatabaseRow'] === $input['databaseRow']) {
return true;
}
return false;
}))->shouldBeCalled()->willReturnArgument(0);
$this->subject->addData($input);
}
/**
* @test
*/
public function addDataCallsFlexFormSegmentGroupForDummyContainerAndAddsFlexParentDatabaseRow()
{
$input = [
'tableName' => 'aTable',
'databaseRow' => [
'aField' => [
'data' => [],
],
'pointerField' => 'aFlex',
],
'processedTca' => [
'columns' => [
'aField' => [
'config' => [
'type' => 'flex',
'ds_pointerField' => 'pointerField',
'ds' => [
'sheets' => [
'sDEF' => [
'ROOT' => [
'type' => 'array',
'el' => [
'aFlexField' => [
'label' => 'aFlexFieldLabel',
'config' => [
'type' => 'input',
],
],
],
],
],
],
],
],
],
],
],
'pageTsConfig' => [],
];
/** @var FlexFormSegment|ObjectProphecy $dummyGroupExisting */
$dummyGroupExisting = $this->prophesize(FlexFormSegment::class);
GeneralUtility::addInstance(FlexFormSegment::class, $dummyGroupExisting->reveal());
// Check array given to flex group contains databaseRow as flexParentDatabaseRow and check compile is called
$dummyGroupExisting->compile(Argument::that(function ($result) use ($input) {
if ($result['flexParentDatabaseRow'] === $input['databaseRow']) {
return true;
}
return false;
}))->shouldBeCalled()->willReturnArgument(0);
$this->subject->addData($input);
}
}
==============================================
Breaking: #70132 - FormEngine custom functions
==============================================
Description
===========
Due to the refactoring of the backend FormEngine code the "low end" extension API to manipulate data
changed. Affected are especially the ``type=user`` ``TCA`` element, any ``userFunc`` configured in
``TCA`` as well as the ``itemsProcFunc`` to manipulate single items in select, group and other types.
In general data given to those custom functions has changed and extensions that rely on this data may
fail. For instance, if a ``itemsProcFunc`` was defined for a field within a flex form, the ``row``
array argument contained the full parent database row in the past. This is no longer the case and
the parent database row is now transferred as ``flexParentDatabaseRow``. In other cases data previously
handed over to custom functions may no longer be available at all.
Impact
======
Custom functions receive less or different options than before and may stop working.
Affected Installations
======================
Extensions using the ``TCA`` with ``type=user`` fields, extensions using ``TCA`` with ``userFunc`` and
extensions using ``itemsProcFunc``.
Migration
=========
Developers using this API must debug the data given to custom functions and adapt accordingly.
If the data given is not sufficient it is possible to register own element classes with the
``NodeFactory`` or to manipulate data by adding a custom ``FormDataProvider``. While the current
API will be mostly stable throughout further TYPO3 CMS 7 LTS patch releases, it may however happen
that the given API and data breaks again with the development of the TYPO3 CMS 8 path to make the
FormEngine code more powerful and reliable in the end.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment