[BUGFIX] FormEngine: Custom functions 42/44542/4
authorChristian Kuhn <lolli@schwarzbu.ch>
Wed, 4 Nov 2015 20:29:04 +0000 (21:29 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 5 Nov 2015 11:36:40 +0000 (12:36 +0100)
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: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Classes/Form/FormDataCompiler.php
typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php
typo3/sysext/backend/Tests/Unit/Form/FormDataCompilerTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaCheckboxItemsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexProcessTest.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-70132-FormEngineCustomFunctions.rst [new file with mode: 0644]

index 7438a49..1c6469f 100644 (file)
@@ -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' => [],
index 6f421e5..be68154 100644 (file)
@@ -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);
index 2c9f9d4..361337e 100644 (file)
@@ -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)) {
index 78150c8..6bdd8a0 100644 (file)
@@ -136,6 +136,16 @@ class FormDataComplierTest extends UnitTestCase
     /**
      * @test
      */
+    public function compileThrowsExceptionIfFormDataGroupDoesNotReturnArray()
+    {
+        $this->formDataGroupProphecy->compile(Argument::cetera())->willReturn(null);
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1446664764);
+        $this->subject->compile([]);
+    }
+
+    /**
+     * @test
+     */
     public function compileThrowsExceptionIfFormDataGroupRemovedKeysFromResultArray()
     {
         $this->formDataGroupProphecy->compile(Argument::cetera())->will(function ($arguments) {
index 5307431..0ded17c 100644 (file)
@@ -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);
     }
+
 }
index dc2f142..6ece71e 100644 (file)
@@ -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);
+    }
+
 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-70132-FormEngineCustomFunctions.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-70132-FormEngineCustomFunctions.rst
new file mode 100644 (file)
index 0000000..8d78be6
--- /dev/null
@@ -0,0 +1,41 @@
+==============================================
+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.