[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaSelectTreeItems.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\FormDataProvider;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
18 use TYPO3\CMS\Core\Tree\TableConfiguration\ExtJsArrayTreeRenderer;
19 use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
20 use TYPO3\CMS\Core\Tree\TableConfiguration\TreeDataProviderFactory;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\MathUtility;
23
24 /**
25 * Data provider for type=select + renderType=selectTree fields.
26 *
27 * Used in combination with SelectTreeElement to create the base HTML for trees,
28 * does a little bit of sanitation and preparation then.
29 *
30 * Used in combination with FormSelectTreeAjaxController to fetch the final tree list, this is
31 * triggered if $result['selectTreeCompileItems'] is set to true. This way the tree item
32 * calculation is only triggered if needed in this ajax context. Writes the prepared
33 * item array to ['config']['items'] in this case.
34 */
35 class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProviderInterface
36 {
37 /**
38 * Sanitize config options and resolve select items if requested.
39 *
40 * @param array $result
41 * @return array
42 * @throws \UnexpectedValueException
43 */
44 public function addData(array $result)
45 {
46 $table = $result['tableName'];
47
48 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
49 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') {
50 continue;
51 }
52
53 // Make sure we are only processing supported renderTypes
54 if (!$this->isTargetRenderType($fieldConfig)) {
55 continue;
56 }
57
58 $fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange($fieldConfig['config']['maxitems'], 0, 99999);
59 if ($fieldConfig['config']['maxitems'] === 0) {
60 $fieldConfig['config']['maxitems'] = 99999;
61 }
62
63 // A couple of tree specific config parameters can be overwritten via page TS.
64 // Pick those that influence the data fetching and write them into the config
65 // given to the tree data provider. This is additionally used in SelectTreeElement, so always do that.
66 if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['config.']['treeConfig.'])) {
67 $pageTsConfig = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['config.']['treeConfig.'];
68 // If rootUid is set in pageTsConfig, use it
69 if (isset($pageTsConfig['rootUid'])) {
70 $fieldConfig['config']['treeConfig']['rootUid'] = (int)$pageTsConfig['rootUid'];
71 }
72 if (isset($pageTsConfig['appearance.']['expandAll'])) {
73 $fieldConfig['config']['treeConfig']['appearance']['expandAll'] = (bool)$pageTsConfig['appearance.']['expandAll'];
74 }
75 if (isset($pageTsConfig['appearance.']['maxLevels'])) {
76 $fieldConfig['config']['treeConfig']['appearance']['maxLevels'] = (int)$pageTsConfig['appearance.']['maxLevels'];
77 }
78 if (isset($pageTsConfig['appearance.']['nonSelectableLevels'])) {
79 $fieldConfig['config']['treeConfig']['appearance']['nonSelectableLevels'] = $pageTsConfig['appearance.']['nonSelectableLevels'];
80 }
81 }
82
83 if ($result['selectTreeCompileItems']) {
84 // Prepare the list of currently selected nodes using RelationHandler
85 $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
86 $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, []);
87
88 $finalItems = [];
89
90 // Prepare the list of "static" items if there are any.
91 // "static" and "dynamic" is separated since the tree code only copes with "real" existing foreign nodes,
92 // so this "static" stuff allows defining tree items that don't really exist in the tree.
93 $itemsFromTca = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName);
94 // List of additional items defined by page ts config "addItems"
95 $itemsFromPageTsConfig = $this->addItemsFromPageTsConfig($result, $fieldName, []);
96 if (!empty($itemsFromTca) || !empty($itemsFromPageTsConfig)) {
97 // First apply "keepItems" to $itemsFromTca, this will restrict the tca item list to only
98 // those items that are defined in page ts "keepItems" if given
99 $itemsFromTca = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $itemsFromTca);
100 // Then, merge the items from page ts "addItems" into item list, since "addItems" should
101 // add additional items even if they are not in the "keepItems" list
102 $staticItems = array_merge($itemsFromTca, $itemsFromPageTsConfig);
103 // Now apply page ts config "removeItems", so this is *after* addItems, so "removeItems" could
104 // possibly remove items again that were added via "addItems"
105 $staticItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $staticItems);
106 // Now, apply user and access right restrictions to this item list
107 $staticItems = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $staticItems);
108 $staticItems = $this->removeItemsByUserAuthMode($result, $fieldName, $staticItems);
109 $staticItems = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $staticItems);
110 // Call itemsProcFunc if given. Note this function does *not* see the "dynamic" list of items
111 if (!empty($fieldConfig['config']['itemsProcFunc'])) {
112 $staticItems = $this->resolveItemProcessorFunction($result, $fieldName, $staticItems);
113 // itemsProcFunc must not be used anymore
114 unset($fieldConfig['config']['itemsProcFunc']);
115 }
116 // And translate any labels from the static list
117 $staticItems = $this->translateLabels($result, $staticItems, $table, $fieldName);
118 // Now compile the target items using the same array structure as the "dynamic" list below
119 foreach ($staticItems as $item) {
120 if ($item[1] === '--div--') {
121 // Skip divs that may occur here for whatever reason
122 continue;
123 }
124 $finalItems[] = [
125 'identifier' => $item[1],
126 'name' => $item[0],
127 'icon' => $item[2] ?? '',
128 'iconOverlay' => '',
129 'depth' => 0,
130 'hasChildren' => false,
131 'selectable' => true,
132 'checked' => in_array($item[1], $result['databaseRow'][$fieldName]),
133 ];
134 }
135 }
136
137 // Fetch the list of all possible "related" items (yuk!) and apply a similar processing as with the "static" list
138 $dynamicItems = $this->addItemsFromForeignTable($result, $fieldName, []);
139 $dynamicItems = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $dynamicItems);
140 $dynamicItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $dynamicItems);
141 $dynamicItems = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $dynamicItems);
142 $dynamicItems = $this->removeItemsByUserAuthMode($result, $fieldName, $dynamicItems);
143 $dynamicItems = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $dynamicItems);
144 // Funnily, the only data needed for the tree code are the uids of the possible records (yuk!) - get them
145 $uidListOfAllDynamicItems = [];
146 foreach ($dynamicItems as $item) {
147 if ((int)$item[1] > 0) {
148 $uidListOfAllDynamicItems[] = (int)$item[1];
149 }
150 }
151 // Now kick in this tree stuff
152 $treeDataProvider = TreeDataProviderFactory::getDataProvider(
153 $fieldConfig['config'],
154 $table,
155 $fieldName,
156 $result['databaseRow']
157 );
158 $treeDataProvider->setSelectedList(implode(',', $result['databaseRow'][$fieldName]));
159 // Basically the tree foo fetches all tree nodes again (aaargs), then verifies if
160 // a given rows uid is within this "list of allowed uids". It then creates an object
161 // tree representing the nested tree, just to collapse all that to a flat array again. Yay ...
162 $treeDataProvider->setItemWhiteList($uidListOfAllDynamicItems);
163 $treeDataProvider->initializeTreeData();
164 $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
165 $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
166 $tree->setDataProvider($treeDataProvider);
167 $tree->setNodeRenderer($treeRenderer);
168
169 // Merge tree nodes after calculated nodes from static items
170 $fieldConfig['config']['items'] = array_merge($finalItems, $tree->render());
171 }
172
173 $result['processedTca']['columns'][$fieldName] = $fieldConfig;
174 }
175
176 return $result;
177 }
178
179 /**
180 * Determines whether the current field is a valid target for this DataProvider
181 *
182 * @param array $fieldConfig
183 * @return bool
184 */
185 protected function isTargetRenderType(array $fieldConfig)
186 {
187 return $fieldConfig['config']['renderType'] === 'selectTree';
188 }
189 }