[BUGFIX] Flexform suggest wizard no record found 55/47655/5
authorKlaas Johan Kooistra <k.kooistra@youwe.nl>
Thu, 14 Apr 2016 06:29:03 +0000 (08:29 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 14 Apr 2016 19:34:38 +0000 (21:34 +0200)
When using a suggest wizard within a flexform field within another flexform
field of type array and another field is configured after the array field the
suggest configuration is always empty, causing no records to be found.
Fixed by checking whether configuration was found and breaking the loop.

Resolves: #75494
Releases: master
Change-Id: Ie6af67ab3bb236f686dea8528205ee676cf9181d
Reviewed-on: https://review.typo3.org/47655
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Susanne Moog <typo3@susannemoog.de>
Tested-by: Susanne Moog <typo3@susannemoog.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Classes/Form/Wizard/SuggestWizard.php
typo3/sysext/backend/Tests/Unit/Form/Wizard/SuggestWizardTest.php [new file with mode: 0644]

index dcd1330..1e51fb5 100644 (file)
@@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Lang\LanguageService;
 
 /**
@@ -99,34 +100,6 @@ class SuggestWizard
     }
 
     /**
-     * Search a data structure array recursively -- including within nested
-     * (repeating) elements -- for a particular field config.
-     *
-     * @param array $dataStructure The data structure
-     * @param string $fieldName The field name
-     * @return array
-     */
-    protected function getNestedDsFieldConfig(array $dataStructure, $fieldName)
-    {
-        $fieldConfig = array();
-        $elements = $dataStructure['ROOT']['el'] ? $dataStructure['ROOT']['el'] : $dataStructure['el'];
-        if (is_array($elements)) {
-            foreach ($elements as $k => $ds) {
-                if ($k === $fieldName) {
-                    $fieldConfig = $ds['TCEforms']['config'];
-                    break;
-                } elseif (isset($ds['el'][$fieldName]['TCEforms']['config'])) {
-                    $fieldConfig = $ds['el'][$fieldName]['TCEforms']['config'];
-                    break;
-                } else {
-                    $fieldConfig = $this->getNestedDsFieldConfig($ds, $fieldName);
-                }
-            }
-        }
-        return $fieldConfig;
-    }
-
-    /**
      * Ajax handler for the "suggest" feature in FormEngine.
      *
      * @param ServerRequestInterface $request
@@ -277,7 +250,6 @@ class SuggestWizard
             $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
         } else {
             $parts = explode('|', $field);
-
             if ($GLOBALS['TCA'][$table]['columns'][$parts[0]]['config']['type'] !== 'flex') {
                 return;
             }
@@ -295,15 +267,8 @@ class SuggestWizard
             }
             $flexformDSArray = BackendUtility::getFlexFormDS($flexfieldTCAConfig, $row, $table, $parts[0]);
             $flexformDSArray = GeneralUtility::resolveAllSheetsInDS($flexformDSArray);
-            $flexformElement = $parts[count($parts) - 2];
-            foreach ($flexformDSArray as $sheet) {
-                foreach ($sheet as $_ => $dataStructure) {
-                    $fieldConfig = $this->getNestedDsFieldConfig($dataStructure, $flexformElement);
-                    if (!empty($fieldConfig)) {
-                        break(2);
-                    }
-                }
-            }
+
+            $fieldConfig = $this->getFieldConfiguration($parts, $flexformDSArray);
             // Flexform field name levels are separated with | instead of encapsulation in [];
             // reverse this here to be compatible with regular field names.
             $field = str_replace('|', '][', $field);
@@ -311,6 +276,107 @@ class SuggestWizard
     }
 
     /**
+     * Get configuration for given field by traversing the flexform path to field
+     * given in $parts
+     *
+     * @param array $parts
+     * @param array $flexformDSArray
+     * @return array
+     */
+    protected function getFieldConfiguration(array $parts, array $flexformDSArray)
+    {
+        $relevantParts = [];
+        foreach ($parts as $part) {
+            if ($this->isRelevantFlexField($part)) {
+                $relevantParts[] = $part;
+            }
+        }
+        // throw away db field name for flexform field
+        array_shift($relevantParts);
+
+        $flexformElement = array_pop($relevantParts);
+        $sheetName = array_shift($relevantParts);
+        $flexSubDataStructure = $flexformDSArray['sheets'][$sheetName];
+        foreach ($relevantParts as $relevantPart) {
+            $flexSubDataStructure = $this->getSubConfigurationForSections($flexSubDataStructure, $relevantPart);
+        }
+        $fieldConfig = $this->getNestedDsFieldConfig($flexSubDataStructure, $flexformElement);
+        return $fieldConfig;
+    }
+
+    /**
+     * Recursively get sub sections in data structure by name
+     *
+     * @param array $dataStructure
+     * @param string $fieldName
+     * @return array
+     */
+    protected function getSubConfigurationForSections(array $dataStructure, $fieldName)
+    {
+        $fieldConfig = array();
+        $elements = $dataStructure['ROOT']['el'] ? $dataStructure['ROOT']['el'] : $dataStructure['el'];
+        if (is_array($elements)) {
+            foreach ($elements as $k => $ds) {
+                if ($k === $fieldName) {
+                    $fieldConfig = $ds;
+                    break;
+                } elseif (isset($ds['el'][$fieldName])) {
+                    $fieldConfig = $ds['el'][$fieldName];
+                    break;
+                } else {
+                    $fieldConfig = $this->getSubConfigurationForSections($ds, $fieldName);
+                }
+            }
+        }
+        return $fieldConfig;
+    }
+
+    /**
+     * Search a data structure array recursively -- including within nested
+     * (repeating) elements -- for a particular field config.
+     *
+     * @param array $dataStructure The data structure
+     * @param string $fieldName The field name
+     * @return array
+     */
+    protected function getNestedDsFieldConfig(array $dataStructure, $fieldName)
+    {
+        $fieldConfig = array();
+        $elements = $dataStructure['ROOT']['el'] ? $dataStructure['ROOT']['el'] : $dataStructure['el'];
+        if (is_array($elements)) {
+            foreach ($elements as $k => $ds) {
+                if ($k === $fieldName) {
+                    $fieldConfig = $ds['TCEforms']['config'];
+                    break;
+                } elseif (isset($ds['el'][$fieldName]['TCEforms']['config'])) {
+                    $fieldConfig = $ds['el'][$fieldName]['TCEforms']['config'];
+                    break;
+                } else {
+                    $fieldConfig = $this->getNestedDsFieldConfig($ds, $fieldName);
+                }
+            }
+        }
+        return $fieldConfig;
+    }
+
+    /**
+     * Checks whether the field is an actual identifier or just "array filling"
+     *
+     * @param string $fieldName
+     * @return bool
+     */
+    protected function isRelevantFlexField($fieldName)
+    {
+        return !(
+            StringUtility::beginsWith($fieldName, 'ID-') ||
+            $fieldName === 'lDEF' ||
+            $fieldName === 'vDEF' ||
+            $fieldName === 'data' ||
+            $fieldName === 'el'
+        );
+    }
+
+    /**
      * Returns the configuration for the suggest wizard for the given table. This does multiple overlays from the
      * TSconfig.
      *
@@ -359,7 +425,6 @@ class SuggestWizard
      *
      * @param array $resultRows
      * @param int $maxItems
-     * @param string $rowIdSuffix
      * @return array
      */
     protected function createListItemsFromResultRow(array $resultRows, $maxItems)
diff --git a/typo3/sysext/backend/Tests/Unit/Form/Wizard/SuggestWizardTest.php b/typo3/sysext/backend/Tests/Unit/Form/Wizard/SuggestWizardTest.php
new file mode 100644 (file)
index 0000000..b80e222
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Unit\Form\Wizard;
+
+/*
+ * 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 TYPO3\CMS\Backend\Form\Wizard\SuggestWizard;
+use TYPO3\CMS\Core\Tests\AccessibleObjectInterface;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+
+/**
+ * Test case
+ */
+class SuggestWizardTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getFieldConfigurationFetchesConfigurationDependentOnTheFullPathToField()
+    {
+        $config = [
+            'el' => [
+                'content' => [
+                    'TCEforms' => [
+                        'config' => [
+                            'Sublevel field configuration',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+
+        $dataStructure['sheets']['sSuggestCheckCombination']['ROOT']['el'] = [
+            'settings.topname1' => [
+                'el' => [
+                    'item' => [
+                        'el' => [
+                            'content' => [
+                                'TCEforms' => [
+                                    'config' => [
+                                        'different foo config for field with same name',
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+            'settings.topname3' => [
+                'el' => ['item' => $config]
+            ],
+            'settings.topname2' => [
+                'el' => [
+                    'item' => [
+                        'el' => [
+                            'content' => [
+                                'TCEforms' => [
+                                    'config' => [
+                                        'different foo config for field with same name',
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $parts = [
+            0 => 'flex_1',
+            1 => 'data',
+            2 => 'sSuggestCheckCombination',
+            3 => 'lDEF',
+            4 => 'settings.topname3',
+            5 => 'el',
+            6 => 'ID-efa3ff7ed5-idx1460636854058-form',
+            7 => 'item',
+            8 => 'el',
+            9 => 'content',
+            10 => 'vDEF',
+        ];
+
+        /** @var SuggestWizard|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $subject */
+        $subject = $this->getAccessibleMock(SuggestWizard::class, array('getNestedDsFieldConfig'), array(), '', false);
+        $subject
+            ->expects($this->once())
+            ->method('getNestedDsFieldConfig')
+            ->with($config, 'content');
+        $subject->_call('getFieldConfiguration', $parts, $dataStructure);
+    }
+
+    /**
+     * @test
+     */
+    public function getFieldConfigurationFetchesConfigurationForFieldsWithoutSheets()
+    {
+        $config = [
+            'ROOT' => [
+                'type' => 'array',
+                'el' => [
+                    'content' => [
+                        'TCEforms' => [
+                            'label' => 'group_db_1 wizard suggest',
+                            'config' => [
+                                'type' => 'group',
+                                'internal_type' => 'db',
+                                'allowed' => 'tx_styleguide_staticdata',
+                                'wizards' => [
+                                    'suggest' => [
+                                        'type' => 'suggest',
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ]
+        ];
+        $dataStructure = [
+            'sheets' => [
+                'sDEF' => $config
+            ],
+        ];
+        $parts = [
+            0 => 'flex_1',
+            1 => 'data',
+            2 => 'sDEF',
+            3 => 'lDEF',
+            4 => 'content',
+            5 => 'vDEF',
+        ];
+
+        /** @var SuggestWizard|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $subject */
+        $subject = $this->getAccessibleMock(SuggestWizard::class, array('getNestedDsFieldConfig'), array(), '', false);
+        $subject
+            ->expects($this->once())
+            ->method('getNestedDsFieldConfig')
+            ->with($config, 'content');
+
+        $subject->_call('getFieldConfiguration', $parts, $dataStructure);
+    }
+}
\ No newline at end of file