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