a533c4de4842d9d670fb5ac32a20fde69c7a81ea
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Tree / TableConfiguration / DatabaseTreeDataProvider.php
1 <?php
2 namespace TYPO3\CMS\Core\Tree\TableConfiguration;
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\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Extbase\Object\ObjectManager;
24 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
25
26 /**
27 * TCA tree data provider
28 */
29 class DatabaseTreeDataProvider extends AbstractTableConfigurationTreeDataProvider
30 {
31 const SIGNAL_PostProcessTreeData = 'PostProcessTreeData';
32 const MODE_CHILDREN = 1;
33 const MODE_PARENT = 2;
34
35 /**
36 * @var string
37 */
38 protected $tableName = '';
39
40 /**
41 * @var string
42 */
43 protected $treeId = '';
44
45 /**
46 * @var string
47 */
48 protected $labelField = '';
49
50 /**
51 * @var string
52 */
53 protected $tableWhere = '';
54
55 /**
56 * @var int
57 */
58 protected $lookupMode = self::MODE_CHILDREN;
59
60 /**
61 * @var string
62 */
63 protected $lookupField = '';
64
65 /**
66 * @var int
67 */
68 protected $rootUid = 0;
69
70 /**
71 * @var array
72 */
73 protected $idCache = [];
74
75 /**
76 * Stores TCA-Configuration of the LookUpField in tableName
77 *
78 * @var array
79 */
80 protected $columnConfiguration;
81
82 /**
83 * node sort values (the orderings from foreign_Table_where evaluation)
84 *
85 * @var array
86 */
87 protected $nodeSortValues = [];
88
89 /**
90 * @var array TCEforms compiled TSConfig array
91 */
92 protected $generatedTSConfig = [];
93
94 /**
95 * @var Dispatcher
96 */
97 protected $signalSlotDispatcher;
98
99 /**
100 * Sets the label field
101 *
102 * @param string $labelField
103 * @return void
104 */
105 public function setLabelField($labelField)
106 {
107 $this->labelField = $labelField;
108 }
109
110 /**
111 * Gets the label field
112 *
113 * @return string
114 */
115 public function getLabelField()
116 {
117 return $this->labelField;
118 }
119
120 /**
121 * Sets the table name
122 *
123 * @param string $tableName
124 * @return void
125 */
126 public function setTableName($tableName)
127 {
128 $this->tableName = $tableName;
129 }
130
131 /**
132 * Gets the table name
133 *
134 * @return string
135 */
136 public function getTableName()
137 {
138 return $this->tableName;
139 }
140
141 /**
142 * Sets the lookup field
143 *
144 * @param string $lookupField
145 * @return void
146 */
147 public function setLookupField($lookupField)
148 {
149 $this->lookupField = $lookupField;
150 }
151
152 /**
153 * Gets the lookup field
154 *
155 * @return string
156 */
157 public function getLookupField()
158 {
159 return $this->lookupField;
160 }
161
162 /**
163 * Sets the lookup mode
164 *
165 * @param int $lookupMode
166 * @return void
167 */
168 public function setLookupMode($lookupMode)
169 {
170 $this->lookupMode = $lookupMode;
171 }
172
173 /**
174 * Gets the lookup mode
175 *
176 * @return int
177 */
178 public function getLookupMode()
179 {
180 return $this->lookupMode;
181 }
182
183 /**
184 * Gets the nodes
185 *
186 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
187 * @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
188 */
189 public function getNodes(\TYPO3\CMS\Backend\Tree\TreeNode $node)
190 {
191 }
192
193 /**
194 * Gets the root node
195 *
196 * @return \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode
197 */
198 public function getRoot()
199 {
200 return $this->buildRepresentationForNode($this->treeData);
201 }
202
203 /**
204 * Sets the root uid
205 *
206 * @param int $rootUid
207 * @return void
208 */
209 public function setRootUid($rootUid)
210 {
211 $this->rootUid = $rootUid;
212 }
213
214 /**
215 * Gets the root uid
216 *
217 * @return int
218 */
219 public function getRootUid()
220 {
221 return $this->rootUid;
222 }
223
224 /**
225 * Sets the tableWhere clause
226 *
227 * @param string $tableWhere
228 * @return void
229 */
230 public function setTableWhere($tableWhere)
231 {
232 $this->tableWhere = $tableWhere;
233 }
234
235 /**
236 * Gets the tableWhere clause
237 *
238 * @return string
239 */
240 public function getTableWhere()
241 {
242 return $this->tableWhere;
243 }
244
245 /**
246 * Builds a complete node including childs
247 *
248 * @param \TYPO3\CMS\Backend\Tree\TreeNode $basicNode
249 * @param NULL|\TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode $parent
250 * @param int $level
251 * @return \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode Node object
252 */
253 protected function buildRepresentationForNode(\TYPO3\CMS\Backend\Tree\TreeNode $basicNode, DatabaseTreeNode $parent = null, $level = 0)
254 {
255 /** @var $node \TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode */
256 $node = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode::class);
257 $row = [];
258 if ($basicNode->getId() == 0) {
259 $node->setSelected(false);
260 $node->setExpanded(true);
261 $node->setLabel($GLOBALS['LANG']->sL($GLOBALS['TCA'][$this->tableName]['ctrl']['title']));
262 } else {
263 $row = BackendUtility::getRecordWSOL($this->tableName, $basicNode->getId(), '*', '', false);
264 $node->setLabel(BackendUtility::getRecordTitle($this->tableName, $row) ?: $basicNode->getId());
265 $node->setSelected(GeneralUtility::inList($this->getSelectedList(), $basicNode->getId()));
266 $node->setExpanded($this->isExpanded($basicNode));
267 }
268 $node->setId($basicNode->getId());
269 $node->setSelectable(!GeneralUtility::inList($this->getNonSelectableLevelList(), $level) && !in_array($basicNode->getId(), $this->getItemUnselectableList()));
270 $node->setSortValue($this->nodeSortValues[$basicNode->getId()]);
271 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
272 $node->setIcon($iconFactory->getIconForRecord($this->tableName, $row, Icon::SIZE_SMALL));
273 $node->setParentNode($parent);
274 if ($basicNode->hasChildNodes()) {
275 $node->setHasChildren(true);
276 /** @var $childNodes \TYPO3\CMS\Backend\Tree\SortedTreeNodeCollection */
277 $childNodes = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\SortedTreeNodeCollection::class);
278 foreach ($basicNode->getChildNodes() as $child) {
279 $childNodes->append($this->buildRepresentationForNode($child, $node, $level + 1));
280 }
281 $node->setChildNodes($childNodes);
282 }
283 return $node;
284 }
285
286 /**
287 * Init the tree data
288 *
289 * @return void
290 */
291 public function initializeTreeData()
292 {
293 parent::initializeTreeData();
294 $this->nodeSortValues = array_flip($this->itemWhiteList);
295 $this->columnConfiguration = $GLOBALS['TCA'][$this->getTableName()]['columns'][$this->getLookupField()]['config'];
296 if (isset($this->columnConfiguration['foreign_table']) && $this->columnConfiguration['foreign_table'] != $this->getTableName()) {
297 throw new \InvalidArgumentException('TCA Tree configuration is invalid: tree for different node-Tables is not implemented yet', 1290944650);
298 }
299 $this->treeData = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNode::class);
300 $this->loadTreeData();
301 $this->emitPostProcessTreeDataSignal();
302 }
303
304 /**
305 * Loads the tree data (all possible children)
306 *
307 * @return void
308 */
309 protected function loadTreeData()
310 {
311 $this->treeData->setId($this->getRootUid());
312 $this->treeData->setParentNode(null);
313 if ($this->levelMaximum >= 1) {
314 $childNodes = $this->getChildrenOf($this->treeData, 1);
315 if ($childNodes !== null) {
316 $this->treeData->setChildNodes($childNodes);
317 }
318 }
319 }
320
321 /**
322 * Gets node children
323 *
324 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
325 * @param int $level
326 * @return NULL|\TYPO3\CMS\Backend\Tree\TreeNodeCollection
327 */
328 protected function getChildrenOf(\TYPO3\CMS\Backend\Tree\TreeNode $node, $level)
329 {
330 $nodeData = null;
331 if ($node->getId() !== 0) {
332 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
333 ->getQueryBuilderForTable($this->getTableName());
334 $queryBuilder->getRestrictions()->removeAll();
335 $nodeData = $queryBuilder->select('*')
336 ->from($this->getTableName())
337 ->where($queryBuilder->expr()->eq('uid', $node->getId()))
338 ->setMaxResults(1)
339 ->execute()
340 ->fetch();
341 }
342 if (empty($nodeData)) {
343 $nodeData = [
344 'uid' => 0,
345 $this->getLookupField() => ''
346 ];
347 }
348 $storage = null;
349 $children = $this->getRelatedRecords($nodeData);
350 if (!empty($children)) {
351 /** @var $storage \TYPO3\CMS\Backend\Tree\TreeNodeCollection */
352 $storage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNodeCollection::class);
353 foreach ($children as $child) {
354 $node = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\TreeNode::class);
355 $node->setId($child);
356 if ($level < $this->levelMaximum) {
357 $children = $this->getChildrenOf($node, $level + 1);
358 if ($children !== null) {
359 $node->setChildNodes($children);
360 }
361 }
362 $storage->append($node);
363 }
364 }
365 return $storage;
366 }
367
368 /**
369 * Gets related records depending on TCA configuration
370 *
371 * @param array $row
372 * @return array
373 */
374 protected function getRelatedRecords(array $row)
375 {
376 if ($this->getLookupMode() == DatabaseTreeDataProvider::MODE_PARENT) {
377 $children = $this->getChildrenUidsFromParentRelation($row);
378 } else {
379 $children = $this->getChildrenUidsFromChildrenRelation($row);
380 }
381 $allowedArray = [];
382 foreach ($children as $child) {
383 if (!in_array($child, $this->idCache) && in_array($child, $this->itemWhiteList)) {
384 $allowedArray[] = $child;
385 }
386 }
387 $this->idCache = array_merge($this->idCache, $allowedArray);
388 return $allowedArray;
389 }
390
391 /**
392 * Gets related records depending on TCA configuration
393 *
394 * @param array $row
395 * @return array
396 */
397 protected function getChildrenUidsFromParentRelation(array $row)
398 {
399 $uid = $row['uid'];
400 switch ((string)$this->columnConfiguration['type']) {
401 case 'inline':
402
403 case 'select':
404 if ($this->columnConfiguration['MM']) {
405 /** @var $dbGroup \TYPO3\CMS\Core\Database\RelationHandler */
406 $dbGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
407 // Dummy field for setting "look from other site"
408 $this->columnConfiguration['MM_oppositeField'] = 'children';
409 $dbGroup->start($row[$this->getLookupField()], $this->getTableName(), $this->columnConfiguration['MM'], $uid, $this->getTableName(), $this->columnConfiguration);
410 $relatedUids = $dbGroup->tableArray[$this->getTableName()];
411 } elseif ($this->columnConfiguration['foreign_field']) {
412 $relatedUids = $this->listFieldQuery($this->columnConfiguration['foreign_field'], $uid);
413 } else {
414 $relatedUids = $this->listFieldQuery($this->getLookupField(), $uid);
415 }
416 break;
417 default:
418 $relatedUids = $this->listFieldQuery($this->getLookupField(), $uid);
419 }
420 return $relatedUids;
421 }
422
423 /**
424 * Gets related children records depending on TCA configuration
425 *
426 * @param array $row
427 * @return array
428 */
429 protected function getChildrenUidsFromChildrenRelation(array $row)
430 {
431 $relatedUids = [];
432 $uid = $row['uid'];
433 $value = $row[$this->getLookupField()];
434 switch ((string)$this->columnConfiguration['type']) {
435 case 'inline':
436 // Intentional fall-through
437 case 'select':
438 if ($this->columnConfiguration['MM']) {
439 $dbGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
440 $dbGroup->start(
441 $value,
442 $this->getTableName(),
443 $this->columnConfiguration['MM'],
444 $uid,
445 $this->getTableName(),
446 $this->columnConfiguration
447 );
448 $relatedUids = $dbGroup->tableArray[$this->getTableName()];
449 } elseif ($this->columnConfiguration['foreign_field']) {
450 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
451 ->getQueryBuilderForTable($this->getTableName());
452 $queryBuilder->getRestrictions()->removeAll();
453 $records = $queryBuilder->select('uid')
454 ->from($this->getTableName())
455 ->where($queryBuilder->expr()->eq($this->columnConfiguration['foreign_field'], (int)$uid))
456 ->execute()
457 ->fetchAll();
458
459 if (!empty($records)) {
460 $relatedUids = array_column($records, 'uid');
461 }
462 } else {
463 $relatedUids = GeneralUtility::intExplode(',', $value, true);
464 }
465 break;
466 default:
467 $relatedUids = GeneralUtility::intExplode(',', $value, true);
468 }
469 return $relatedUids;
470 }
471
472 /**
473 * Queries the table for an field which might contain a list.
474 *
475 * @param string $fieldName the name of the field to be queried
476 * @param int $queryId the uid to search for
477 * @return int[] all uids found
478 */
479 protected function listFieldQuery($fieldName, $queryId)
480 {
481 $queryId = (int)$queryId;
482 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
483 ->getQueryBuilderForTable($this->getTableName());
484 $queryBuilder->getRestrictions()->removeAll();
485
486 $queryBuilder->select('uid')
487 ->from($this->getTableName())
488 ->where($queryBuilder->expr()->inSet($fieldName, $queryId));
489
490 if ($queryId === 0) {
491 $queryBuilder->orWhere(
492 $queryBuilder->expr()->comparison(
493 'CAST(' . $queryBuilder->quoteIdentifier($fieldName) . ' AS CHAR)',
494 ExpressionBuilder::EQ,
495 $queryBuilder->quote('')
496 )
497 );
498 }
499
500 $records = $queryBuilder->execute()->fetchAll();
501 $uidArray = is_array($records) ? array_column($records, 'uid') : [];
502
503 return $uidArray;
504 }
505
506 /**
507 * Emits the post processing tree data signal.
508 *
509 * @return void
510 */
511 protected function emitPostProcessTreeDataSignal()
512 {
513 $this->getSignalSlotDispatcher()->dispatch(
514 DatabaseTreeDataProvider::class,
515 self::SIGNAL_PostProcessTreeData,
516 [$this, $this->treeData]
517 );
518 }
519
520 /**
521 * Get the SignalSlot dispatcher
522 *
523 * @return Dispatcher
524 */
525 protected function getSignalSlotDispatcher()
526 {
527 if (!isset($this->signalSlotDispatcher)) {
528 $this->signalSlotDispatcher = $this->getObjectManager()->get(Dispatcher::class);
529 }
530 return $this->signalSlotDispatcher;
531 }
532
533 /**
534 * Get the ObjectManager
535 *
536 * @return ObjectManager
537 */
538 protected function getObjectManager()
539 {
540 return GeneralUtility::makeInstance(ObjectManager::class);
541 }
542 }