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