[BUGFIX] FormEngine: Fix keepItems, addItems and removeItems handling
[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
23 /**
24 * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
25 */
26 class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProviderInterface
27 {
28 /**
29 * Resolve select items
30 *
31 * @param array $result
32 * @return array
33 * @throws \UnexpectedValueException
34 */
35 public function addData(array $result)
36 {
37 $table = $result['tableName'];
38
39 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
40 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') {
41 continue;
42 }
43
44 // Make sure we are only processing supported renderTypes
45 if (!$this->isTargetRenderType($fieldConfig)) {
46 continue;
47 }
48
49 $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName);
50 $fieldConfig['config']['maxitems'] = $this->sanitizeMaxItems($fieldConfig['config']['maxitems']);
51
52 $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']);
53 $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']);
54 $staticItems = $fieldConfig['config']['items'];
55
56 $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']);
57 $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems);
58
59 $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
60 $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
61 $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
62
63 $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']);
64 $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']);
65 $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']);
66
67 // Resolve "itemsProcFunc"
68 if (!empty($fieldConfig['config']['itemsProcFunc'])) {
69 $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']);
70 // itemsProcFunc must not be used anymore
71 unset($fieldConfig['config']['itemsProcFunc']);
72 }
73
74 // Translate labels
75 $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
76
77 $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems);
78 $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
79 $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
80
81 // Keys may contain table names, so a numeric array is created
82 $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']);
83
84 $fieldConfig['config']['treeData'] = $this->renderTree($result, $fieldConfig, $fieldName, $staticItems);
85
86 $result['processedTca']['columns'][$fieldName] = $fieldConfig;
87 }
88
89 return $result;
90 }
91
92 /**
93 * Renders the Ext JS tree.
94 *
95 * @param array $result The current result array.
96 * @param array $fieldConfig The configuration of the current field.
97 * @param string $fieldName The name of the current field.
98 * @param array $staticItems The static items from the field config.
99 * @return array The tree data configuration
100 */
101 protected function renderTree(array $result, array $fieldConfig, $fieldName, array $staticItems)
102 {
103 $allowedUids = [];
104 foreach ($fieldConfig['config']['items'] as $item) {
105 if ((int)$item[1] > 0) {
106 $allowedUids[] = $item[1];
107 }
108 }
109
110 $treeDataProvider = TreeDataProviderFactory::getDataProvider(
111 $fieldConfig['config'],
112 $result['tableName'],
113 $fieldName,
114 $result['databaseRow']
115 );
116 $treeDataProvider->setSelectedList(is_array($result['databaseRow'][$fieldName]) ? implode(',', $result['databaseRow'][$fieldName]) : $result['databaseRow'][$fieldName]);
117 $treeDataProvider->setItemWhiteList($allowedUids);
118 $treeDataProvider->initializeTreeData();
119
120 /** @var ExtJsArrayTreeRenderer $treeRenderer */
121 $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
122
123 /** @var TableConfigurationTree $tree */
124 $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
125 $tree->setDataProvider($treeDataProvider);
126 $tree->setNodeRenderer($treeRenderer);
127
128 $treeItems = $this->prepareAdditionalItems($staticItems, $result['databaseRow'][$fieldName]);
129 $treeItems[] = $tree->render();
130
131 $treeConfig = [
132 'items' => $treeItems,
133 'selectedNodes' => $this->prepareSelectedNodes($fieldConfig['config']['items'], $result['databaseRow'][$fieldName])
134 ];
135
136 return $treeConfig;
137 }
138
139 /**
140 * Prepare the additional items that get prepended to the tree as leaves
141 *
142 * @param array $itemArray
143 * @param array $selectedNodes
144 * @return array
145 */
146 protected function prepareAdditionalItems(array $itemArray, array $selectedNodes)
147 {
148 $additionalItems = [];
149
150 foreach ($itemArray as $item) {
151 if ($item[1] === '--div--') {
152 continue;
153 }
154
155 $additionalItems[] = [
156 'uid' => $item[1],
157 'text' => $item[0],
158 'selectable' => true,
159 'leaf' => true,
160 'checked' => in_array($item[1], $selectedNodes),
161 'icon' => $item[3]
162 ];
163 }
164
165 return $additionalItems;
166 }
167
168 /**
169 * Re-create the old pipe based syntax of selected nodes for the ExtJS rendering part
170 *
171 * @param array $itemArray
172 * @param array $databaseValues
173 * @return array
174 * @todo: this is ugly - should be removed with the tree rewrite
175 */
176 protected function prepareSelectedNodes(array $itemArray, array $databaseValues)
177 {
178 $selectedNodes = [];
179 if (!empty($databaseValues)) {
180 foreach ($databaseValues as $selectedNode) {
181 foreach ($itemArray as $possibleSelectBoxItem) {
182 if ((string)$possibleSelectBoxItem[1] === (string)$selectedNode) {
183 $selectedNodes[] = $selectedNode . '|' . rawurlencode($possibleSelectBoxItem[0]);
184 }
185 }
186 }
187 }
188
189 return $selectedNodes;
190 }
191
192 /**
193 * Determines whether the current field is a valid target for this DataProvider
194 *
195 * @param array $fieldConfig
196 * @return bool
197 */
198 protected function isTargetRenderType(array $fieldConfig)
199 {
200 return $fieldConfig['config']['renderType'] === 'selectTree';
201 }
202 }