-
Notifications
You must be signed in to change notification settings - Fork 639
/
TcaCategory.php
248 lines (222 loc) · 11.6 KB
/
TcaCategory.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Core\Database\RelationHandler;
use TYPO3\CMS\Core\Tree\TableConfiguration\ArrayTreeRenderer;
use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
use TYPO3\CMS\Core\Tree\TableConfiguration\TreeDataProviderFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
/**
* Data provider for type=category
*
* Used in combination with CategoryElement to create the base HTML for the category tree.
*
* Used in combination with FormSelectTreeAjaxController to fetch the final tree list, this
* is triggered if $result['selectTreeCompileItems'] is set to true. This way the tree item
* calculation is only triggered if needed in this ajax context. Writes the prepared item
* array to ['config']['items'] in this case.
*/
class TcaCategory extends AbstractItemProvider implements FormDataProviderInterface
{
/**
* Sanitize config options and resolve category items if requested.
*
* @param array $result
* @return array
*/
public function addData(array $result): array
{
$table = $result['tableName'];
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
// This data provider only works for type=category
if (($fieldConfig['config']['type'] ?? '') !== 'category') {
continue;
}
// Make sure we are only processing supported renderTypes
if (!$this->isTargetRenderType($fieldConfig)) {
continue;
}
$fieldConfig = $this->initializeDefaultFieldConfig($fieldConfig);
$fieldConfig = $this->parseStartingPointsFromSiteConfiguration($result, $fieldConfig);
$fieldConfig = $this->overrideConfigFromPageTSconfig($result, $table, $fieldName, $fieldConfig);
// Prepare the list of currently selected nodes using RelationHandler
// This is needed to ensure a correct value initialization before the actual tree is loaded
$result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
$result['databaseRow'][$fieldName] = $this->processCategoryFieldValue($result, $fieldName);
// Since AbstractItemProvider does sometimes access $result[...][config] instead of
// our updated $fieldConfig, we have to assign it here and from now on, only work
// with the $result[...][config] array.
$result['processedTca']['columns'][$fieldName] = $fieldConfig;
// This is usually only executed in an ajax request
if ($result['selectTreeCompileItems'] ?? false) {
// Fetch static items from TCA and TSconfig. Since this is
// not supported, throw an exception if something was found.
$staticItems = $this->sanitizeItemArray($result['processedTca']['columns'][$fieldName]['config']['items'] ?? [], $table, $fieldName);
$tsConfigItems = $this->addItemsFromPageTsConfig($result, $fieldName, []);
if ($staticItems !== [] || $tsConfigItems !== []) {
throw new \RuntimeException(
'Static items are not supported for field ' . $fieldName . ' from table ' . $table . ' with type category',
1627336557
);
}
// Fetch the list of all possible "related" items and apply processing
// @todo: This uses 4th param 'true' as hack to add the full item rows speeding up
// processing of items in the tree class construct below. Simplify the construct:
// The entire $treeDataProvider / $treeRenderer / $tree construct should probably
// vanish and the tree processing could happen here in the data provider? Watch
// out for the permission event in the tree construct when doing this.
$dynamicItems = $this->addItemsFromForeignTable($result, $fieldName, [], true);
// Remove items as configured via TsConfig
$dynamicItems = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $dynamicItems);
$dynamicItems = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $dynamicItems);
// Finally, the only data needed for the tree code are the valid uids of the possible records
$uidListOfAllDynamicItems = array_map('intval', array_filter(
array_values(array_column($dynamicItems, 1)),
static fn($uid) => (int)$uid > 0
));
$fullRowsOfDynamicItems = [];
foreach ($dynamicItems as $item) {
// @todo: Prepare performance hack for tree calculation below.
if (isset($item['_row'])) {
$fullRowsOfDynamicItems[(int)$item['_row']['uid']] = $item['_row'];
}
}
// Initialize the tree data provider
$treeDataProvider = TreeDataProviderFactory::getDataProvider(
$result['processedTca']['columns'][$fieldName]['config'],
$table,
$fieldName,
$result['databaseRow']
);
$treeDataProvider->setSelectedList(implode(',', $result['databaseRow'][$fieldName]));
// Basically the tree data provider fetches all tree nodes again and
// then verifies if a given rows' uid is within the item whitelist.
// @todo: Simplify construct, probably remove entirely. See @todo above as well.
$treeDataProvider->setAvailableItems($fullRowsOfDynamicItems);
$treeDataProvider->setItemWhiteList($uidListOfAllDynamicItems);
$treeDataProvider->initializeTreeData();
$treeRenderer = GeneralUtility::makeInstance(ArrayTreeRenderer::class);
$tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
$tree->setDataProvider($treeDataProvider);
$tree->setNodeRenderer($treeRenderer);
// Add the calculated tree nodes
$result['processedTca']['columns'][$fieldName]['config']['items'] = $tree->render();
}
}
return $result;
}
/**
* A couple of tree specific config parameters can be overwritten via page TS.
* Pick those that influence the data fetching and write them into the config
* given to the tree data provider.
*/
protected function overrideConfigFromPageTSconfig(
array $result,
string $table,
string $fieldName,
array $fieldConfig
): array {
$pageTsConfig = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['config.']['treeConfig.'] ?? [];
if (!is_array($pageTsConfig) || $pageTsConfig === []) {
return $fieldConfig;
}
if (isset($pageTsConfig['rootUid'])) {
trigger_error(sprintf('The setting "TCEFORM.%1$s.%2$s.config.treeConfig.rootUid" is marked as deprecated. Consider using "TCEFORM.%1$s.%2$s.config.treeConfig.startingPoints" instead.', $table, $fieldName), E_USER_DEPRECATED);
$fieldConfig['config']['treeConfig']['startingPoints'] = (string)(int)$pageTsConfig['rootUid'];
}
if (isset($pageTsConfig['startingPoints'])) {
$fieldConfig['config']['treeConfig']['startingPoints'] = implode(',', array_unique(GeneralUtility::intExplode(',', $pageTsConfig['startingPoints'])));
}
if (isset($pageTsConfig['appearance.']['expandAll'])) {
$fieldConfig['config']['treeConfig']['appearance']['expandAll'] = (bool)$pageTsConfig['appearance.']['expandAll'];
}
if (isset($pageTsConfig['appearance.']['maxLevels'])) {
$fieldConfig['config']['treeConfig']['appearance']['maxLevels'] = (int)$pageTsConfig['appearance.']['maxLevels'];
}
if (isset($pageTsConfig['appearance.']['nonSelectableLevels'])) {
$fieldConfig['config']['treeConfig']['appearance']['nonSelectableLevels'] = $pageTsConfig['appearance.']['nonSelectableLevels'];
}
return $fieldConfig;
}
/**
* Validate and sanitize the category field value.
*
* @param array $result
* @param string $fieldName
* @return array
*/
protected function processCategoryFieldValue(array $result, string $fieldName): array
{
$fieldConfig = $result['processedTca']['columns'][$fieldName];
$relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
$newDatabaseValueArray = [];
$currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
$relationHandler->start(
implode(',', $currentDatabaseValueArray),
$fieldConfig['config']['foreign_table'],
$fieldConfig['config']['MM'],
$result['databaseRow']['uid'],
$result['tableName'],
$fieldConfig['config']
);
$newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
} else {
// If not dealing with MM relations, use default live uid, not versioned uid for record relations
$relationHandler->start(
implode(',', $currentDatabaseValueArray),
$fieldConfig['config']['foreign_table'],
'',
$this->getLiveUid($result),
$result['tableName'],
$fieldConfig['config']
);
$databaseIds = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
// remove all items from the current DB values if not available as relation
$newDatabaseValueArray = array_values(array_intersect($currentDatabaseValueArray, $databaseIds));
}
// Since only uids are allowed, the array must be unique
return array_unique($newDatabaseValueArray);
}
protected function isTargetRenderType($fieldConfig): bool
{
// Type category does not support any renderType
return !isset($fieldConfig['config']['renderType']);
}
protected function initializeDefaultFieldConfig(array $fieldConfig): array
{
$fieldConfig = array_replace_recursive([
'config' => [
'treeConfig' => [
'parentField' => 'parent',
'appearance' => [
'expandAll' => true,
'showHeader' => true,
'maxLevels' => 99,
],
],
],
], $fieldConfig);
// Calculate maxitems value, while 0 will fall back to 99999
$fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange(
$fieldConfig['config']['maxitems'] ?? 0,
0,
99999
) ?: 99999;
return $fieldConfig;
}
}