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